~ci-train-bot/account-polld/account-polld-ubuntu-yakkety-landing-054

« back to all changes in this revision

Viewing changes to plugins/facebook/facebook.go

  • Committer: Sergio Schvezov
  • Date: 2014-08-23 20:00:22 UTC
  • mfrom: (27.11.1 qtcontact)
  • mto: This revision was merged to the branch mainline in revision 69.
  • Revision ID: sergio.schvezov@canonical.com-20140823200022-eh501y1cuuz98fbx
Merged qtcontact into loop.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
/*
2
2
 Copyright 2014 Canonical Ltd.
3
3
 Authors: James Henstridge <james.henstridge@canonical.com>
 
4
          Sergio Schvezov <sergio.schvezov@canonical.com>
4
5
 
5
6
 This program is free software: you can redistribute it and/or modify it
6
7
 under the terms of the GNU General Public License version 3, as published
19
20
 
20
21
import (
21
22
        "encoding/json"
 
23
        "fmt"
22
24
        "net/http"
23
25
        "net/url"
 
26
        "os"
 
27
        "strings"
 
28
        "time"
 
29
 
 
30
        "log"
24
31
 
25
32
        "launchpad.net/account-polld/accounts"
 
33
        "launchpad.net/account-polld/gettext"
26
34
        "launchpad.net/account-polld/plugins"
27
35
)
28
36
 
 
37
const (
 
38
        facebookTime                        = "2006-01-02T15:04:05-0700"
 
39
        maxIndividualNotifications          = 4
 
40
        consolidatedNotificationsIndexStart = maxIndividualNotifications
 
41
        pluginName                          = "facebook"
 
42
)
 
43
 
29
44
var baseUrl, _ = url.Parse("https://graph.facebook.com/v2.0/")
30
45
 
 
46
type timeStamp string
 
47
 
 
48
func (stamp timeStamp) persist(accountId uint) (err error) {
 
49
        err = plugins.Persist(pluginName, accountId, stamp)
 
50
        if err != nil {
 
51
                log.Print("facebook plugin", accountId, ": failed to save state: ", err)
 
52
        }
 
53
        return nil
 
54
}
 
55
 
 
56
func timeStampFromStorage(accountId uint) (stamp timeStamp, err error) {
 
57
        err = plugins.FromPersist(pluginName, accountId, &stamp)
 
58
        if err != nil {
 
59
                return stamp, err
 
60
        }
 
61
        if _, err := time.Parse(facebookTime, string(stamp)); err == nil {
 
62
                return stamp, err
 
63
        }
 
64
        return stamp, nil
 
65
}
 
66
 
31
67
type fbPlugin struct {
32
 
        lastUpdate string
 
68
        lastUpdate timeStamp
 
69
        accountId  uint
33
70
}
34
71
 
35
 
func New() plugins.Plugin {
36
 
        return &fbPlugin{}
 
72
func New(accountId uint) plugins.Plugin {
 
73
        stamp, err := timeStampFromStorage(accountId)
 
74
        if err != nil {
 
75
                log.Print("facebook plugin ", accountId, ": cannot load previous state from storage: ", err)
 
76
        } else {
 
77
                log.Print("facebook plugin ", accountId, ": last state loaded from storage")
 
78
        }
 
79
        return &fbPlugin{lastUpdate: stamp, accountId: accountId}
37
80
}
38
81
 
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 {
77
121
                return nil, err
78
122
        }
79
 
        pushMsg := []plugins.PushMessage{}
80
 
        latestUpdate := ""
 
123
 
 
124
        var validNotifications []notification
 
125
        latestUpdate := p.lastUpdate
81
126
        for _, n := range result.Data {
82
127
                if n.UpdatedTime <= p.lastUpdate {
83
 
                        continue
84
 
                }
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)
 
131
                } else {
 
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
 
136
                        }
90
137
                }
91
138
        }
92
139
        p.lastUpdate = latestUpdate
 
140
        p.lastUpdate.persist(p.accountId)
 
141
 
 
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 {
 
147
                        break
 
148
                }
 
149
        }
 
150
 
 
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
 
157
                        }
 
158
                }
 
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, "...")
 
165
                                break
 
166
                        }
 
167
                }
 
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))
 
176
                }
 
177
        }
 
178
 
93
179
        return pushMsg, nil
94
180
}
95
181
 
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
 
186
        }
97
187
        resp, err := p.request(authData, "me/notifications")
98
188
        if err != nil {
99
189
                return nil, err
101
191
        return p.parseResponse(resp)
102
192
}
103
193
 
 
194
func toEpoch(stamp timeStamp) int64 {
 
195
        if t, err := time.Parse(facebookTime, string(stamp)); err == nil {
 
196
                return t.Unix()
 
197
        }
 
198
        return time.Now().Unix()
 
199
}
 
200
 
104
201
// The notifications response format is described here:
105
202
// https://developers.facebook.com/docs/graph-api/reference/v2.0/user/notifications/
106
203
type notificationDoc struct {
112
209
}
113
210
 
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"`
 
222
}
 
223
 
 
224
func (n notification) picture() string {
 
225
        u, err := baseUrl.Parse(fmt.Sprintf("%s/picture", n.From.Id))
 
226
        if err != nil {
 
227
                log.Println("facebook plugin: cannot get picture for", n.Id)
 
228
                return ""
 
229
        }
 
230
        query := u.Query()
 
231
        query.Add("redirect", "true")
 
232
        u.RawQuery = query.Encode()
 
233
        return u.String()
125
234
}
126
235
 
127
236
type object struct {