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) (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 data := renderer.NewThreadPageTemplateData(status, context, reply, id)
250 err = svc.renderer.RenderThreadPage(ctx, client, data)
258 func (svc *service) Like(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
259 _, err = c.Favourite(ctx, id)
263 func (svc *service) UnLike(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
264 _, err = c.Unfavourite(ctx, id)
268 func (svc *service) Retweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
269 _, err = c.Reblog(ctx, id)
273 func (svc *service) UnRetweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error) {
274 _, err = c.Unreblog(ctx, id)
278 func (svc *service) PostTweet(ctx context.Context, client io.Writer, c *mastodon.Client, content string, replyToID string) (err error) {
279 tweet := &mastodon.Toot{
281 InReplyToID: replyToID,
283 _, err = c.PostStatus(ctx, tweet)