Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Commit

Permalink
Add support for multiple feeds
Browse files Browse the repository at this point in the history
  • Loading branch information
falzm committed May 19, 2019
1 parent 1d6acb6 commit 7e3df8c
Show file tree
Hide file tree
Showing 30 changed files with 610 additions and 1,877 deletions.
40 changes: 18 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,30 +29,26 @@ Run `instafeed -h` for usage help.

`instafeed` expects the `IG_LOGIN` and `IG_PASSWORD` environment variables set to your Instagram login and password respectively, and the username of the Instagram user provided as argument. On successful execution, it prints the resulting RSS feed on the standard output.

If multiple Instagram users arguments are provided, `instafeed` will bulk all retrieved posts into a single feed sorted in reverse chronological order (i.e. from newest to oldest).

Example:

```
```console
$ export IG_LOGIN="your_instagram_login" IG_PASSWORD="********"
$ ./instafeed marutaro > marutaro.xml
$ cat marutaro.xml
<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Instagram Feed: Shinjiro Ono</title>
<link>https://www.instagram.com/marutaro</link>
<description>My official shop is in 2-12-3 Nezu Bunkyo-ku Japan✨🐶✨ まるのお店は 根津駅徒歩1分にあるよ!👇ブログ更新したよ👇</description>
<managingEditor>[email protected] (Shinjiro Ono)</managingEditor>
<pubDate>Tue, 03 Apr 2018 15:45:15 +0200</pubDate>
<item>
<title>You&#39;ve had a tough day. Thanks for your hard wo...</title>
<link>https://www.instagram.com/p/BhGv7aMnkAm</link>
<description>You&#39;ve had a tough day. Thanks for your hard wo...</description>
<content:encoded><![CDATA[<p>You've had a tough day. Thanks for your hard work.✨🐶✨おつまる〜\(^o^)/ 今日もよく頑張ったね!
#今日のお昼は何食べて来たの❓
#っていうか昨日のお昼何食べたか覚えてる❓</p><p><img src="https://scontent-cdg2-1.cdninstagram.com/vp/398dc348ba778a65a8313936c3b3b6b4/5B5A6F96/t51.2885-15/s750x750/sh0.08/e35/29417321_2040161612665341_6846638084159700992_n.jpg?ig_cache_key=MTc0OTI5NjI5NjA0NDE1MDgyMg%3D%3D.2"></p>]]></content:encoded>
<pubDate>Tue, 03 Apr 2018 12:46:30 +0200</pubDate>
</item>
...
</channel>
</rss>

# Fetching a single user feed
$ instafeed marutaro > marutaro.xml

# Fetching multiple user feeds, limiting to 10 posts per user
$ instafeed -n 10 marutaro nala_cat realgrumpycat > feeds.xml

# Fetching multiple user feeds from a list
$ cat > ~/.instagram_users <<EOF
marutaro
nala_cat
realgrumpycat
EOF
$ instafeed -n 10 -l ~/.instagram_users > feeds.xml
```

In order to avoid re-logging into Instagram upon each execution, `instafeed` stores the user profile into a local file that is read at runtime (your password is **not** stored). By default this file is located at `$HOME/.instafeed`, but a different path can be specified using the `-f` option.
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ go 1.12

require (
github.com/Masterminds/goutils v1.0.1
github.com/ahmdrz/goinsta/v2 v2.4.1-0.20190323085700-ffd9e29a313a
github.com/ahmdrz/goinsta/v2 v2.4.1-0.20190510152949-903ea3101245
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261 // indirect
github.com/getsentry/raven-go v0.2.0
github.com/gorilla/feeds v1.1.0
github.com/pkg/errors v0.0.0-20180311214515-816c9085562c // indirect
github.com/gorilla/feeds v1.1.1-0.20190321233042-2079b9bbce59
github.com/pkg/errors v0.8.1
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/Masterminds/goutils v1.0.1 h1:upyB/JGR/aPHTE3f4O3mBIbJCr7aPup+/03IS4a
github.com/Masterminds/goutils v1.0.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/ahmdrz/goinsta/v2 v2.4.1-0.20190323085700-ffd9e29a313a h1:lvtGW57Exf0L6PtsPNkKRIW9oY6bxPqVx1zr8KYTtfM=
github.com/ahmdrz/goinsta/v2 v2.4.1-0.20190323085700-ffd9e29a313a/go.mod h1:M6/Tnc41f4gn+rl9HxFaNY1c6Px5zQWHgyY6S/wSZFs=
github.com/ahmdrz/goinsta/v2 v2.4.1-0.20190510152949-903ea3101245 h1:W4UU/mMm1UKBCb0a+XpWPsVi3GB3aTiA1UlkwTR+VLU=
github.com/ahmdrz/goinsta/v2 v2.4.1-0.20190510152949-903ea3101245/go.mod h1:M6/Tnc41f4gn+rl9HxFaNY1c6Px5zQWHgyY6S/wSZFs=
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261 h1:6/yVvBsKeAw05IUj4AzvrxaCnDjN4nUqKjW9+w5wixg=
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/getsentry/raven-go v0.0.0-20180319154601-7562301a6763 h1:hHg8bhpQT8qI9LNuz//aNIs7sleRfxb4flXYu95lx7U=
Expand All @@ -10,5 +12,9 @@ github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JY
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/gorilla/feeds v1.1.0 h1:pcgLJhbdYgaUESnj3AmXPcB7cS3vy63+jC/TI14AGXk=
github.com/gorilla/feeds v1.1.0/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
github.com/gorilla/feeds v1.1.1-0.20190321233042-2079b9bbce59 h1:QPsOLFscpHFlsZJdjgIM/b0T2f+oNnS91zDllaXyeQQ=
github.com/gorilla/feeds v1.1.1-0.20190321233042-2079b9bbce59/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
github.com/pkg/errors v0.0.0-20180311214515-816c9085562c h1:F5RoIh7F9wB47PvXvpP1+Ihq1TkyC8iRdvwfKkESEZQ=
github.com/pkg/errors v0.0.0-20180311214515-816c9085562c/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
114 changes: 77 additions & 37 deletions instafeed.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@ package main
import (
"flag"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"time"

"github.com/Masterminds/goutils"
"github.com/ahmdrz/goinsta/v2"
sentry "github.com/getsentry/raven-go"
"github.com/gorilla/feeds"
"github.com/pkg/errors"
)

var (
insta *goinsta.Instagram
sendtoSentry bool
configFile string
listFile string
feedMaxItems int
)

Expand All @@ -26,21 +31,40 @@ func init() {

flag.StringVar(&configFile, "f", path.Join(os.Getenv("HOME"), ".instafeed"),
"Path to file where to store profile configuration")
flag.StringVar(&listFile, "l", "", "Path to file containing list of Instagram users")
flag.IntVar(&feedMaxItems, "n", 20, "Number of user feed items")
flag.Parse()
}

func main() {
var (
insta *goinsta.Instagram
err error
igUsers []string
err error
)

if len(os.Args) != 2 {
igUsers = flag.Args()
if listFile != "" {
data, err := ioutil.ReadFile(listFile)
if err != nil {
dieOnError("unable to read user list file: %s", err)
}

igUsers = strings.Split(string(data), "\n")

if len(igUsers) == 0 {
dieOnError("no users found in list file")
}

// Remove empty item caused by trailing line from file
if igUsers[len(igUsers)-1] == "" {
igUsers = igUsers[:len(igUsers)-1]
}
}

if len(igUsers) == 0 && len(flag.Args()) == 0 {
dieOnError("missing Instagram username argument")
}

igUser := os.Args[1]
igLogin := os.Getenv("IG_LOGIN")
igPassword := os.Getenv("IG_PASSWORD")

Expand All @@ -62,58 +86,69 @@ func main() {
fmt.Fprintf(os.Stderr, "error: unable to export Instagram client configuration: %s\n", err)
}

user, err := insta.Profiles.ByName(igUser)
feed := &feeds.Feed{
Title: fmt.Sprintf("Instagram"),
Link: &feeds.Link{Href: "https://www.instagram.com/"},
Description: "Instagram RSS feed generated by Instafeed (https://github.com/falzm/instafeed)",
Created: time.Now(),
}

for _, u := range igUsers {
items, err := fetchUserFeedItems(u)
if err != nil {
dieOnError(fmt.Sprintf("unable to retrieve user %q feed: %s", u, err))
}

for _, item := range items {
feed.Add(item)
}
}

feed.Sort(func(a, b *feeds.Item) bool {
return a.Created.After(b.Created)
})

rss, err := feed.ToRss()
if err != nil {
dieOnError("unable to get user information: %s", err)
dieOnError("unable to render RSS feed: %s", err)
}

latest := user.Feed()
fmt.Println(rss)
}

func fetchUserFeedItems(name string) ([]*feeds.Item, error) {
items := make([]*feeds.Item, 0)

user, err := insta.Profiles.ByName(name)
if err != nil {
dieOnError("unable to get user latest feed: %s", err)
return nil, errors.Wrap(err, "unable to get user information")
}

feed := &feeds.Feed{
Title: fmt.Sprintf("Instagram Feed: %s", user.FullName),
Link: &feeds.Link{Href: fmt.Sprintf("https://www.instagram.com/%s", igUser)},
Author: &feeds.Author{
Name: user.FullName,
Email: user.Username + "@instagram.com",
},
Description: user.Biography,
Created: time.Now(),
latest := user.Feed()
if err != nil {
return nil, errors.Wrap(err, "unable to get user latest feed")
}

for latest.Next(false) {
for _, item := range latest.Items {
fi, err := formatFeedContent(&item)
if err != nil {
dieOnError("unable to format feed item: %s", err)
}

feed.Add(fi)
if len(feed.Items) >= feedMaxItems {
goto done
items = append(items, formatFeedItem(&item))
if len(items) >= feedMaxItems {
return items, nil
}
}

if err := latest.Error(); err != nil {
if err := latest.Error(); err == goinsta.ErrNoMore {
break
}
dieOnError("unable to retrieve user feed: %s", err)
return nil, errors.Wrap(err, "unable to retrieve user feed")
}
}

done:
rss, err := feed.ToRss()
if err != nil {
dieOnError("unable to render RSS feed: %s", err)
}

fmt.Println(rss)
return items, nil
}

func formatFeedContent(item *goinsta.Item) (*feeds.Item, error) {
func formatFeedItem(item *goinsta.Item) *feeds.Item {
shortDesc, _ := goutils.Abbreviate(item.Caption.Text, 50)

content := fmt.Sprintf("<p>%s</p>", item.Caption.Text)
Expand All @@ -129,12 +164,17 @@ func formatFeedContent(item *goinsta.Item) (*feeds.Item, error) {
}

return &feeds.Item{
Title: shortDesc,
Created: time.Unix(item.TakenAt, 0),
Id: item.ID,
Title: shortDesc,
Created: time.Unix(item.TakenAt, 0),
Author: &feeds.Author{
Name: fmt.Sprintf("%s (@%s)", item.User.FullName, item.User.Username),
Email: item.User.Username + "@instagram.com",
},
Description: shortDesc,
Content: content,
Link: &feeds.Link{Href: fmt.Sprintf("https://www.instagram.com/p/%s", item.Code)},
}, nil
}
}

func dieOnError(format string, a ...interface{}) {
Expand Down
21 changes: 0 additions & 21 deletions vendor/github.com/ahmdrz/goinsta/LICENSE

This file was deleted.

Loading

0 comments on commit 7e3df8c

Please sign in to comment.