api.go 4.35 KB
Newer Older
zauberstuhl's avatar
zauberstuhl committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
/*
 * Unmappd - Visualize your Untappd checkins
 * Copyright (C) 2019 Lukas Matt <lukas@matt.wf>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package main

import (
  "time"
  "net/http"
  "io/ioutil"
  "encoding/json"
  "fmt"
  "log"

  "github.com/spf13/viper"
)

const rootUrl = "https://api.untappd.com"

type Uresp struct {
  Response struct {
    Pagination struct {
      Max_id int
    }
    Checkins struct {
      Items []struct {
        Checkin_id uint
        Created_at string
        Venue interface{}
      }
    }
  }
}

type Uauthresp struct {
  Response struct {
    Access_token string
  }
}

type Uinfo struct {
  Response struct {
    User struct {
      User_name string
59 60 61 62 63 64 65 66 67
      Stats struct {
        Total_badges int
        Total_friends int
        Total_checkins int
        Total_beers int
        Total_created_beers int
        Total_followings int
        Total_photos int
      }
zauberstuhl's avatar
zauberstuhl committed
68 69 70 71
    }
  }
}

72
func auth_handler(w http.ResponseWriter, r *http.Request) {
zauberstuhl's avatar
zauberstuhl committed
73 74 75 76 77 78 79 80 81 82
  db, err := openDatabase()
  if err != nil {
    log.Print(err)
    http.Redirect(w, r, "/", 301)
    return
  }
  defer db.Close()

  userParam := viper.GetString("nginxRewrite")
  if userParam == "" {
83
    userParam = "/users/?user=%s"
zauberstuhl's avatar
zauberstuhl committed
84 85 86 87 88 89 90 91 92 93
  }

  if codes, ok := r.URL.Query()["code"]; ok {
    token, err := fetch_auth(codes[0])
    if err != nil {
      log.Print("Cannot fetch auth token: ", err)
      http.Redirect(w, r, "/", 301)
      return
    }

94 95
    info, err := fetch_user_info(token)
    username := info.Response.User.User_name
zauberstuhl's avatar
zauberstuhl committed
96 97 98 99 100 101 102 103 104 105 106 107 108
    if err != nil || username == "" {
      log.Print("Cannot fetch user info: ", err)
      http.Redirect(w, r, "/", 301)
      return
    }

    err = db.First(&User{}, "username = ?", username).Error
    if err == nil {
      http.Redirect(w, r,
        fmt.Sprintf(userParam, username), 301)
      return
    }

109
    var stats = info.Response.User.Stats
zauberstuhl's avatar
zauberstuhl committed
110 111 112
    var user = User{
      CreatedAt: time.Now(),
      UpdatedAt: time.Now(),
113 114 115 116 117 118 119
      TotalBadges: stats.Total_beers,
      TotalFriends: stats.Total_friends,
      TotalCheckins: stats.Total_checkins,
      TotalBeers: stats.Total_beers,
      TotalCreatedBeers: stats.Total_created_beers,
      TotalFollowings: stats.Total_followings,
      TotalPhotos: stats.Total_photos,
zauberstuhl's avatar
zauberstuhl committed
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
      Username: username,
      Token: token,
    }

    err = db.Create(&user).Error
    if err != nil {
      log.Print("Cannot create user: ", err)
      http.Redirect(w, r, "/", 301)
      return
    }

    http.Redirect(w, r,
      fmt.Sprintf(userParam, user.Username), 301)
    return
  }
  http.Redirect(w, r, "/", 301)
}

func fetch_auth(code string) (string, error) {
  var uauth Uauthresp
  return uauth.Response.Access_token, get(fmt.Sprintf(
    "https://untappd.com/oauth/authorize/?client_id=%s&client_secret=%s&response_type=code&redirect_url=%s&code=%s",
    CLIENTID, CLIENTSECRET, REDIRECTURL, code), &uauth)
}

145
func fetch_user_info(token string) (info Uinfo, err error) {
zauberstuhl's avatar
zauberstuhl committed
146 147
  url := fmt.Sprintf(
    "%s/v4/user/info/?access_token=%s", rootUrl, token)
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
  return info, get(url, &info)
}

func fetch_checkins(user *User, id uint, next bool) (resp Uresp, err error) {
  url := fmt.Sprintf(
    "%s/v4/user/checkins/%s?access_token=%s",
    rootUrl, user.Username, user.Token)

  if id > 0 {
    param := "min_id"
    if next {
      param = "max_id"
    }
    url = fmt.Sprintf("%s&%s=%d", url, param, id)
  }

  log.Print("url ", url)
  return resp, get(url, &resp)
zauberstuhl's avatar
zauberstuhl committed
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
}

func get(url string, v interface{}) error {
  resp, err := http.Get(url)
  if err != nil {
    log.Print("http.Get", err)
    return err
  }
  defer resp.Body.Close()

  body, err := ioutil.ReadAll(resp.Body)
  if err != nil {
    log.Print("ioutil.ReadAll", err)
    return err
  }

  if viper.GetBool("debug") {
    log.Printf("get url body: %s\n", string(body))
  }
  return json.Unmarshal(body, v)
}