22 ErrInvalidArgument = errors.New("invalid argument")
23 ErrInvalidToken = errors.New("invalid token")
24 ErrInvalidClient = errors.New("invalid client")
27 type Service interface {
28 ServeHomePage(ctx context.Context, client io.Writer) (err error)
29 GetAuthUrl(ctx context.Context, instance string) (url string, sessionID string, err error)
30 GetUserToken(ctx context.Context, sessionID string, c *mastodon.Client, token string) (accessToken string, err error)
31 ServeErrorPage(ctx context.Context, client io.Writer, err error)
32 ServeSigninPage(ctx context.Context, client io.Writer) (err error)
33 ServeTimelinePage(ctx context.Context, client io.Writer, c *mastodon.Client, maxID string, sinceID string, minID string) (err error)
34 ServeThreadPage(ctx context.Context, client io.Writer, c *mastodon.Client, id string, reply bool) (err error)
35 Like(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)
36 UnLike(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)
37 Retweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)
38 UnRetweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)
39 PostTweet(ctx context.Context, client io.Writer, c *mastodon.Client, content string, replyToID string) (id string, err error)
46 renderer renderer.Renderer
47 sessionRepo model.SessionRepository
48 appRepo model.AppRepository
51 func NewService(clientName string, clientScope string, clientWebsite string,
52 renderer renderer.Renderer, sessionRepo model.SessionRepository,
53 appRepo model.AppRepository) Service {
55 clientName: clientName,
56 clientScope: clientScope,
57 clientWebsite: clientWebsite,
59 sessionRepo: sessionRepo,
64 func (svc *service) GetAuthUrl(ctx context.Context, instance string) (
65 redirectUrl string, sessionID string, err error) {
66 if !strings.HasPrefix(instance, "https://") {
67 instance = "https://" + instance
70 sessionID = util.NewSessionId()
71 err = svc.sessionRepo.Add(model.Session{
73 InstanceURL: instance,
79 app, err := svc.appRepo.Get(instance)
81 if err != model.ErrAppNotFound {
85 var mastoApp *mastodon.Application
86 mastoApp, err = mastodon.RegisterApp(ctx, &mastodon.AppConfig{
88 ClientName: svc.clientName,
89 Scopes: svc.clientScope,
90 Website: svc.clientWebsite,
91 RedirectURIs: svc.clientWebsite + "/oauth_callback",
98 InstanceURL: instance,
99 ClientID: mastoApp.ClientID,
100 ClientSecret: mastoApp.ClientSecret,
103 err = svc.appRepo.Add(app)
109 u, err := url.Parse(path.Join(instance, "/oauth/authorize"))
114 q := make(url.Values)
115 q.Set("scope", "read write follow")
116 q.Set("client_id", app.ClientID)
117 q.Set("response_type", "code")
118 q.Set("redirect_uri", svc.clientWebsite+"/oauth_callback")
119 u.RawQuery = q.Encode()
121 redirectUrl = u.String()
126 func (svc *service) GetUserToken(ctx context.Context, sessionID string, c *mastodon.Client,
127 code string) (token string, err error) {
129 err = ErrInvalidArgument
133 session, err := svc.sessionRepo.Get(sessionID)
138 app, err := svc.appRepo.Get(session.InstanceURL)
143 data := &bytes.Buffer{}
144 err = json.NewEncoder(data).Encode(map[string]string{
145 "client_id": app.ClientID,
146 "client_secret": app.ClientSecret,
147 "grant_type": "authorization_code",
149 "redirect_uri": svc.clientWebsite + "/oauth_callback",
155 resp, err := http.Post(app.InstanceURL+"/oauth/token", "application/json", data)
159 defer resp.Body.Close()
162 AccessToken string `json:"access_token"`
165 err = json.NewDecoder(resp.Body).Decode(&res)
170 err = c.AuthenticateToken(ctx, code, svc.clientWebsite+"/oauth_callback")
174 err = svc.sessionRepo.Update(sessionID, c.GetAccessToken(ctx))
177 return res.AccessToken, nil
180 func (svc *service) ServeHomePage(ctx context.Context, client io.Writer) (err error) {
181 err = svc.renderer.RenderHomePage(ctx, client)
189 func (svc *service) ServeErrorPage(ctx context.Context, client io.Writer, err error) {
190 svc.renderer.RenderErrorPage(ctx, client, err)
193 func (svc *service) ServeSigninPage(ctx context.Context, client io.Writer) (err error) {
194 err = svc.renderer.RenderSigninPage(ctx, client)
202 func (svc *service) ServeTimelinePage(ctx context.Context, client io.Writer,
203 c *mastodon.Client, maxID string, sinceID string, minID string) (err error) {
205 var hasNext, hasPrev bool
206 var nextLink, prevLink string
208 var pg = mastodon.Pagination{
215 statuses, err := c.GetTimelineHome(ctx, &pg)
220 if len(pg.MaxID) > 0 {
222 nextLink = fmt.Sprintf("/timeline?max_id=%s", pg.MaxID)
224 if len(pg.SinceID) > 0 {
226 prevLink = fmt.Sprintf("/timeline?since_id=%s", pg.SinceID)
229 data := renderer.NewTimelinePageTemplateData(statuses, hasNext, nextLink, hasPrev, prevLink)
230 err = svc.renderer.RenderTimelinePage(ctx, client, data)
238 func (svc *service) ServeThreadPage(ctx context.Context, client io.Writer, c *mastodon.Client, id string, reply bool) (err error) {
239 status, err := c.GetStatus(ctx, id)
244 context, err := c.GetStatusContext(ctx, id)
249 u, err := c.GetAccountCurrentUser(ctx)
256 if u.ID != status.Account.ID {
257 content += "@" + status.Account.Acct + " "
259 for _, m := range status.Mentions {
261 content += "@" + m.Acct + " "
266 data := renderer.NewThreadPageTemplateData(status, context, reply, id, content)
267 err = svc.renderer.RenderThreadPage(ctx, client, data)
275 func (svc *service) Like(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
276 _, err = c.Favourite(ctx, id)
280 func (svc *service) UnLike(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
281 _, err = c.Unfavourite(ctx, id)
285 func (svc *service) Retweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
286 _, err = c.Reblog(ctx, id)
290 func (svc *service) UnRetweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
291 _, err = c.Unreblog(ctx, id)
295 func (svc *service) PostTweet(ctx context.Context, client io.Writer, c *mastodon.Client, content string, replyToID string) (id string, err error) {
296 tweet := &mastodon.Toot{
298 InReplyToID: replyToID,
301 s, err := c.PostStatus(ctx, tweet)