func Save(db *sqlite3.Conn, table string, uid string, obj *jason.Object) error { handle_err := func(col string, e error) { if e != nil { logger.Error("Error inserting %v into %v: %v", col, table, e.Error()) } } err := db.Exec(fmt.Sprintf("insert or ignore into %v ( uid ) values (?)", table), uid) handle_err("", err) for k, v := range obj.Map() { if k == "uid" { continue } // Are we a string or a number? if i, err := v.Int64(); err == nil { err = db.Exec(fmt.Sprintf("update %v set %v = ? where uid = ?", table, k), i, uid) handle_err(k, err) } if f, err := v.Float64(); err == nil { err = db.Exec(fmt.Sprintf("update %v set %v = ? where uid = ?", table, k), f, uid) handle_err(k, err) } if s, err := v.String(); err == nil { err = db.Exec(fmt.Sprintf("update %v set %v = ? where uid = ?", table, k), s, uid) handle_err(k, err) } if arr, err := obj.GetFloat64Array(k); err == nil { s := fmt.Sprintf("%v", arr) err = db.Exec(fmt.Sprintf("update %v set %v = ? where uid = ?", table, k), s, uid) handle_err(k, err) } } return err }
// Internal helper function to get a string from a jason.Object func jasonGetString(o *jason.Object, key string) string { if o == nil || key == "" { return "" } v, _ := o.GetString(key) return v }
// Internal helper function to get a string slice from a jason.Object func jasonGetStringArray(o *jason.Object, key string) []string { if o == nil || key == "" { return []string{} } v, _ := o.GetStringArray(key) return v }
func PrintLsFromJSON(json *jason.Object, version int, long bool, blobs bool, item string) { var thisVersion int blobArray, _ := json.GetObjectArray("Blobs") versionArray, _ := json.GetObjectArray("Versions") // if version set to zero, use lastest, else use the one given if version == 0 { thisVersion = len(versionArray) } else { thisVersion = version } // Find the version in the JSON, and its subtending slot map versionElement := versionArray[thisVersion-1] slotMap, _ := versionElement.GetObject("Slots") // sort the Slot Keys (filenames) in an array keyMap := []string{} for key, _ := range slotMap.Map() { keyMap = append(keyMap, key) } sort.Strings(keyMap) // Print the slots in the sorted order if long { fmt.Println("Blob Size Date Creator File") fmt.Println("-------------------------------------------------------------------------------------") } for i := range keyMap { if long { blobID, _ := slotMap.GetInt64(keyMap[i]) itemSize, _ := blobArray[blobID-1].GetInt64("Size") saveDate, _ := blobArray[blobID-1].GetString("SaveDate") creator, _ := blobArray[blobID-1].GetString("Creator") fmt.Printf("%03d %12d %s %-8s ", blobID, itemSize, strings.Split(strings.Replace(saveDate, "T", " ", 1), ".")[0], creator) } fmt.Printf("%s/", item) if version != 0 { fmt.Printf("@%d/", thisVersion) } fmt.Printf("%s ", keyMap[i]) fmt.Printf("\n") } }
// Return device make/model from json imageinfo object. func extractCamera(imageinfo *jason.Object) (string, string) { metadata, err := imageinfo.GetObjectArray("commonmetadata") if err != nil { // metadata is null in some cases return "", "" } else { return findCamera(metadata) } }
// CheckAPIKey performs a test API call with the API key // provided in the configuration file to determine if the // service should be enabled. func (yt *YouTube) CheckAPIKey() error { var ( response *http.Response v *jason.Object err error ) if viper.GetString("api_keys.youtube") == "" { return errors.New("No YouTube API key has been provided") } url := "https://www.googleapis.com/youtube/v3/videos?part=snippet&id=KQY9zrjPBjo&key=%s" response, err = http.Get(fmt.Sprintf(url, viper.GetString("api_keys.youtube"))) defer response.Body.Close() if err != nil { return err } if v, err = jason.NewObjectFromReader(response.Body); err != nil { return err } if v, err = v.GetObject("error"); err == nil { message, _ := v.GetString("message") code, _ := v.GetInt64("code") errArray, _ := v.GetObjectArray("errors") reason, _ := errArray[0].GetString("reason") return fmt.Errorf("%d: %s (reason: %s)", code, message, reason) } return nil }
func PrintListFromJSON(json *jason.Object) { versionArray, _ := json.GetObjectArray("Versions") for _, version := range versionArray { versionID, _ := version.GetInt64("ID") saveDate, _ := version.GetString("SaveDate") creator, _ := version.GetString("Creator") note, _ := version.GetString("Note") if note == "" { note = "\"\"" } fmt.Printf("@%02d %s %s %s\n", versionID, strings.Split(strings.Replace(saveDate, "T", " ", 1), ".")[0], creator, note) } }
// Return page data from JSON, or nil if page is "-1", i.e., not found. // Assumes only a single page was requested. func GetJsonPage(json *jason.Object) *jason.Object { pages, err := json.GetObject("query", "pages") if err != nil { panic(err) } for key, value := range pages.Map() { if key == "-1" { return nil } else { valueObj, err := value.Object() if err != nil { panic(err) } return valueObj } } panic("getJsonPage fallthrough") }
func (yt *YouTube) getTrack(id string, submitter *gumble.User, offset time.Duration) (bot.Track, error) { var ( resp *http.Response err error v *jason.Object ) videoURL := "https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id=%s&key=%s" resp, err = http.Get(fmt.Sprintf(videoURL, id, viper.GetString("api_keys.youtube"))) defer resp.Body.Close() if err != nil { return bot.Track{}, err } v, err = jason.NewObjectFromReader(resp.Body) if err != nil { return bot.Track{}, err } items, _ := v.GetObjectArray("items") if len(items) == 0 { return bot.Track{}, errors.New("This YouTube video is private") } item := items[0] title, _ := item.GetString("snippet", "title") thumbnail, _ := item.GetString("snippet", "thumbnails", "high", "url") author, _ := item.GetString("snippet", "channelTitle") durationString, _ := item.GetString("contentDetails", "duration") durationConverted, _ := duration.FromString(durationString) duration := durationConverted.ToDuration() return bot.Track{ ID: id, URL: "https://youtube.com/watch?v=" + id, Title: title, Author: author, Submitter: submitter.Name, Service: yt.ReadableName, Filename: id + ".track", ThumbnailURL: thumbnail, Duration: duration, PlaybackOffset: offset, Playlist: nil, }, nil }
func MakeStubFromJSON(json *jason.Object, item string, pathPrefix string) { versionArray, _ := json.GetObjectArray("Versions") thisVersion := len(versionArray) // Find the version in the JSON, and its subtending slot map versionElement := versionArray[thisVersion-1] slotMap, _ := versionElement.GetObject("Slots") for key, _ := range slotMap.Map() { targetFile := path.Join(pathPrefix, key) // create target directory, return on error targetDir, _ := path.Split(targetFile) err := os.MkdirAll(targetDir, 0755) if err != nil { fmt.Printf("Error: could not create directory %s\n%s\n", err.Error()) return } filePtr, err := os.Create(targetFile) if err != nil { fmt.Printf("Error: could not file directory %s\n%s\n", err.Error()) return } err = filePtr.Close() if err != nil { fmt.Printf("Error: could not close file %s\n%s\n", err.Error()) return } fmt.Println(targetFile) } }
func (f *FileList) BuildListFromJSON(json *jason.Object) { blobArray, _ := json.GetObjectArray("Blobs") versionArray, _ := json.GetObjectArray("Versions") // build lovely mapping of md5 to blobs for _, blob := range blobArray { md5Sum, _ := blob.GetString("MD5") DecodedMD5, _ := base64.StdEncoding.DecodeString(md5Sum) blobID, _ := blob.GetInt64("ID") f.Blobs[hex.EncodeToString(DecodedMD5)] = blobID } for _, version := range versionArray { versionID, _ := version.GetInt64("ID") slotMap, _ := version.GetObject("Slots") for key, _ := range slotMap.Map() { blobID, _ := slotMap.GetInt64(key) md5Sum, _ := blobArray[blobID-1].GetString("MD5") DecodedMD5, _ := base64.StdEncoding.DecodeString(md5Sum) innerMap, ok := f.Files[key] if !ok { innerMap = make(map[int64][]byte) f.Files[key] = innerMap } f.Files[key][blobID] = DecodedMD5 // maps files in latest version to their blobs if versionID == int64(len(versionArray)) { f.Blobs[key] = blobID } } } }
// GetTracks uses the passed URL to find and return // tracks associated with the URL. An error is returned // if any error occurs during the API call. func (sc *SoundCloud) GetTracks(url string, submitter *gumble.User) ([]interfaces.Track, error) { var ( apiURL string err error resp *http.Response v *jason.Object track bot.Track tracks []interfaces.Track ) urlSplit := strings.Split(url, "#t=") apiURL = "http://api.soundcloud.com/resolve?url=%s&client_id=%s" if sc.isPlaylist(url) { // Submitter has added a playlist! resp, err = http.Get(fmt.Sprintf(apiURL, urlSplit[0], viper.GetString("api_keys.soundcloud"))) defer resp.Body.Close() if err != nil { return nil, err } v, err = jason.NewObjectFromReader(resp.Body) if err != nil { return nil, err } title, _ := v.GetString("title") permalink, _ := v.GetString("permalink_url") playlist := &bot.Playlist{ ID: permalink, Title: title, Submitter: submitter.Name, Service: sc.ReadableName, } var scTracks []*jason.Object scTracks, err = v.GetObjectArray("tracks") if err != nil { return nil, err } dummyOffset, _ := time.ParseDuration("0s") for _, t := range scTracks { track, err = sc.getTrack(t, dummyOffset, submitter) if err != nil { // Skip this track. continue } track.Playlist = playlist tracks = append(tracks, track) } if len(tracks) == 0 { return nil, errors.New("Invalid playlist. No tracks were added") } return tracks, nil } // Submitter has added a track! offset := 0 // Calculate track offset if needed if len(urlSplit) == 2 { timeSplit := strings.Split(urlSplit[1], ":") multiplier := 1 for i := len(timeSplit) - 1; i >= 0; i-- { time, _ := strconv.Atoi(timeSplit[i]) offset += time * multiplier multiplier *= 60 } } playbackOffset, _ := time.ParseDuration(fmt.Sprintf("%ds", offset)) resp, err = http.Get(fmt.Sprintf(apiURL, urlSplit[0], viper.GetString("api_keys.soundcloud"))) defer resp.Body.Close() if err != nil { return nil, err } v, err = jason.NewObjectFromReader(resp.Body) if err != nil { return nil, err } track, err = sc.getTrack(v, playbackOffset, submitter) if err != nil { return nil, err } tracks = append(tracks, track) return tracks, nil }
func (sc *SoundCloud) getTrack(obj *jason.Object, offset time.Duration, submitter *gumble.User) (bot.Track, error) { title, _ := obj.GetString("title") idInt, _ := obj.GetInt64("id") id := strconv.FormatInt(idInt, 10) url, _ := obj.GetString("permalink_url") author, _ := obj.GetString("user", "username") authorURL, _ := obj.GetString("user", "permalink_url") durationMS, _ := obj.GetInt64("duration") duration, _ := time.ParseDuration(fmt.Sprintf("%dms", durationMS)) thumbnail, err := obj.GetString("artwork_url") if err != nil { // Track has no artwork, using profile avatar instead. thumbnail, _ = obj.GetString("user", "avatar_url") } return bot.Track{ ID: id, URL: url, Title: title, Author: author, AuthorURL: authorURL, Submitter: submitter.Name, Service: sc.ReadableName, Filename: id + ".track", ThumbnailURL: thumbnail, Duration: duration, PlaybackOffset: offset, Playlist: nil, }, nil }
// GetTracks uses the passed URL to find and return // tracks associated with the URL. An error is returned // if any error occurs during the API call. func (yt *YouTube) GetTracks(url string, submitter *gumble.User) ([]interfaces.Track, error) { var ( playlistURL string playlistItemsURL string id string err error resp *http.Response v *jason.Object track bot.Track tracks []interfaces.Track ) playlistURL = "https://www.googleapis.com/youtube/v3/playlists?part=snippet&id=%s&key=%s" playlistItemsURL = "https://www.googleapis.com/youtube/v3/playlistItems?part=snippet,contentDetails&playlistId=%s&maxResults=%d&key=%s&pageToken=%s" id, err = yt.getID(url) if err != nil { return nil, err } if yt.isPlaylist(url) { resp, err = http.Get(fmt.Sprintf(playlistURL, id, viper.GetString("api_keys.youtube"))) defer resp.Body.Close() if err != nil { return nil, err } v, err = jason.NewObjectFromReader(resp.Body) if err != nil { return nil, err } items, _ := v.GetObjectArray("items") item := items[0] title, _ := item.GetString("snippet", "title") playlist := &bot.Playlist{ ID: id, Title: title, Submitter: submitter.Name, Service: yt.ReadableName, } maxItems := math.MaxInt32 if viper.GetInt("queue.max_tracks_per_playlist") > 0 { maxItems = viper.GetInt("queue.max_tracks_per_playlist") } pageToken := "" for len(tracks) < maxItems { curResp, curErr := http.Get(fmt.Sprintf(playlistItemsURL, id, maxItems, viper.GetString("api_keys.youtube"), pageToken)) defer curResp.Body.Close() if curErr != nil { // An error occurred, simply skip this track. continue } v, err = jason.NewObjectFromReader(curResp.Body) if err != nil { // An error occurred, simply skip this track. continue } curTracks, _ := v.GetObjectArray("items") for _, track := range curTracks { videoID, _ := track.GetString("snippet", "resourceId", "videoId") // Unfortunately we have to execute another API call for each video as the YouTube API does not // return video durations from the playlistItems endpoint... newTrack, _ := yt.getTrack(videoID, submitter) newTrack.Playlist = playlist tracks = append(tracks, newTrack) if len(tracks) >= maxItems { break } } } if len(tracks) == 0 { return nil, errors.New("Invalid playlist. No tracks were added") } return tracks, nil } track, err = yt.getTrack(id, submitter) if err != nil { return nil, err } tracks = append(tracks, track) return tracks, nil }
func parseCondition(v *jason.Object) (c *Condition, err error) { conds, err := v.Object() if conds == nil { fmt.Println("get conds failed") return nil, err } c = &Condition{} for ck, cv := range conds.Map() { ea := &[]Expression{} eo, err := cv.Object() if err != nil { // cv is value e := &Expression{} e.op = "$eq" switch cv.Interface().(type) { case []interface{}: e.value, _ = cv.Array() case bool: e.value, _ = cv.Boolean() case string: e.value, _ = cv.String() case json.Number: e.value, _ = cv.Number() default: continue /* e.value = cv.Interface() fmt.Println(cv, " unsupported type ", reflect.ValueOf(cv.Interface()).Type()) */ } *ea = append(*ea, *e) } else { // cv is a object i := 0 for ek, ev := range eo.Map() { e := &Expression{} e.op = ek switch ev.Interface().(type) { case []interface{}: e.value, _ = ev.Array() case bool: e.value, _ = ev.Boolean() case string: e.value, _ = ev.String() case json.Number: e.value, _ = ev.Number() default: continue /* e.value = ev.Interface() fmt.Println(ev, " unsupported type ", reflect.ValueOf(ev.Interface()).Type()) */ } *ea = append(*ea, *e) i += 1 } } (*c)[ck] = ea } return c, nil }
// GetTracks uses the passed URL to find and return // tracks associated with the URL. An error is returned // if any error occurs during the API call. func (mc *Mixcloud) GetTracks(url string, submitter *gumble.User) ([]interfaces.Track, error) { var ( apiURL string err error resp *http.Response v *jason.Object tracks []interfaces.Track ) apiURL = strings.Replace(url, "www", "api", 1) // Track playback offset is not present in Mixcloud URLs, // so we can safely assume that users will not request // a playback offset in the URL. offset, _ := time.ParseDuration("0s") resp, err = http.Get(apiURL) defer resp.Body.Close() if err != nil { return nil, err } v, err = jason.NewObjectFromReader(resp.Body) if err != nil { return nil, err } id, _ := v.GetString("slug") trackURL, _ := v.GetString("url") title, _ := v.GetString("name") author, _ := v.GetString("user", "username") authorURL, _ := v.GetString("user", "url") durationSecs, _ := v.GetInt64("audio_length") duration, _ := time.ParseDuration(fmt.Sprintf("%ds", durationSecs)) thumbnail, err := v.GetString("pictures", "large") if err != nil { // Track has no artwork, using profile avatar instead. thumbnail, _ = v.GetString("user", "pictures", "large") } track := bot.Track{ ID: id, URL: trackURL, Title: title, Author: author, AuthorURL: authorURL, Submitter: submitter.Name, Service: mc.ReadableName, ThumbnailURL: thumbnail, Filename: id + ".track", Duration: duration, PlaybackOffset: offset, Playlist: nil, } tracks = append(tracks, track) return tracks, nil }