25
32
"launchpad.net/account-polld/accounts"
33
"launchpad.net/account-polld/gettext"
26
34
"launchpad.net/account-polld/plugins"
38
facebookTime = "2006-01-02T15:04:05-0700"
39
maxIndividualNotifications = 4
40
consolidatedNotificationsIndexStart = maxIndividualNotifications
41
pluginName = "facebook"
29
44
var baseUrl, _ = url.Parse("https://graph.facebook.com/v2.0/")
48
func (stamp timeStamp) persist(accountId uint) (err error) {
49
err = plugins.Persist(pluginName, accountId, stamp)
51
log.Print("facebook plugin", accountId, ": failed to save state: ", err)
56
func timeStampFromStorage(accountId uint) (stamp timeStamp, err error) {
57
err = plugins.FromPersist(pluginName, accountId, &stamp)
61
if _, err := time.Parse(facebookTime, string(stamp)); err == nil {
31
67
type fbPlugin struct {
35
func New() plugins.Plugin {
72
func New(accountId uint) plugins.Plugin {
73
stamp, err := timeStampFromStorage(accountId)
75
log.Print("facebook plugin ", accountId, ": cannot load previous state from storage: ", err)
77
log.Print("facebook plugin ", accountId, ": last state loaded from storage")
79
return &fbPlugin{lastUpdate: stamp, accountId: accountId}
39
82
func (p *fbPlugin) ApplicationId() plugins.ApplicationId {
72
115
// page full of notifications. The default limit seems to be
73
116
// 5000 though, which we are unlikely to hit, since
74
117
// notifications are deleted once read.
118
// TODO filter out of date messages before operating
75
119
var result notificationDoc
76
120
if err := decoder.Decode(&result); err != nil {
79
pushMsg := []plugins.PushMessage{}
124
var validNotifications []notification
125
latestUpdate := p.lastUpdate
81
126
for _, n := range result.Data {
82
127
if n.UpdatedTime <= p.lastUpdate {
85
// TODO proper action needed
86
action := "https://m.facebook.com"
87
pushMsg = append(pushMsg, *plugins.NewStandardPushMessage(n.Title, "", action, ""))
88
if n.UpdatedTime > latestUpdate {
89
latestUpdate = n.UpdatedTime
128
log.Println("facebook plugin: skipping notification", n.Id, "as", n.UpdatedTime, "is older than", p.lastUpdate)
129
} else if n.Unread != 1 {
130
log.Println("facebook plugin: skipping notification", n.Id, "as it's read:", n.Unread)
132
log.Println("facebook plugin: valid notification", n.Id, "dated:", n.UpdatedTime, "and read status:", n.Unread)
133
validNotifications = append(validNotifications, n)
134
if n.UpdatedTime > latestUpdate {
135
latestUpdate = n.UpdatedTime
92
139
p.lastUpdate = latestUpdate
140
p.lastUpdate.persist(p.accountId)
142
pushMsg := []plugins.PushMessage{}
143
for _, n := range validNotifications {
144
epoch := toEpoch(n.UpdatedTime)
145
pushMsg = append(pushMsg, *plugins.NewStandardPushMessage(n.From.Name, n.Title, n.Link, n.picture(), epoch))
146
if len(pushMsg) == maxIndividualNotifications {
151
// Now we consolidate the remaining statuses
152
if len(validNotifications) > len(pushMsg) && len(validNotifications) >= consolidatedNotificationsIndexStart {
153
usernamesMap := make(map[string]bool)
154
for _, n := range result.Data[consolidatedNotificationsIndexStart:] {
155
if _, ok := usernamesMap[n.From.Name]; !ok {
156
usernamesMap[n.From.Name] = true
159
usernames := []string{}
160
for k, _ := range usernamesMap {
161
usernames = append(usernames, k)
162
// we don't too many usernames listed, this is a hard number
163
if len(usernames) > 10 {
164
usernames = append(usernames, "...")
168
if len(usernames) > 0 {
169
// TRANSLATORS: This represents a notification summary about more facebook notifications
170
summary := gettext.Gettext("Multiple more notifications")
171
// TRANSLATORS: This represents a notification body with the comma separated facebook usernames
172
body := fmt.Sprintf(gettext.Gettext("From %s"), strings.Join(usernames, ", "))
173
action := "https://m.facebook.com"
174
epoch := time.Now().Unix()
175
pushMsg = append(pushMsg, *plugins.NewStandardPushMessage(summary, body, action, "", epoch))
93
179
return pushMsg, nil
96
182
func (p *fbPlugin) Poll(authData *accounts.AuthData) ([]plugins.PushMessage, error) {
183
// This envvar check is to ease testing.
184
if token := os.Getenv("ACCOUNT_POLLD_TOKEN_FACEBOOK"); token != "" {
185
authData.AccessToken = token
97
187
resp, err := p.request(authData, "me/notifications")
114
211
type notification struct {
115
Id string `json:"id"`
116
From object `json:"from"`
117
To object `json:"to"`
118
CreatedTime string `json:"created_time"`
119
UpdatedTime string `json:"updated_time"`
120
Title string `json:"title"`
121
Link string `json:"link"`
122
Application object `json:"application"`
123
Unread int `json:"unread"`
124
Object object `json:"object"`
212
Id string `json:"id"`
213
From object `json:"from"`
214
To object `json:"to"`
215
CreatedTime timeStamp `json:"created_time"`
216
UpdatedTime timeStamp `json:"updated_time"`
217
Title string `json:"title"`
218
Link string `json:"link"`
219
Application object `json:"application"`
220
Unread int `json:"unread"`
221
Object object `json:"object"`
224
func (n notification) picture() string {
225
u, err := baseUrl.Parse(fmt.Sprintf("%s/picture", n.From.Id))
227
log.Println("facebook plugin: cannot get picture for", n.Id)
231
query.Add("redirect", "true")
232
u.RawQuery = query.Encode()
127
236
type object struct {