-
Notifications
You must be signed in to change notification settings - Fork 0
/
fc2_login.go
158 lines (141 loc) · 3.79 KB
/
fc2_login.go
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package fc2
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"regexp"
"strings"
"time"
"github.com/Darkness4/fc2-live-dl-go/notify/notifier"
"github.com/rs/zerolog/log"
)
// LoginOption is used to configure the login process.
type LoginOption func(*LoginOptions)
// LoginOptions is used to configure the login process.
type LoginOptions struct {
client *http.Client
}
// WithHTTPClient is used to set the HTTP client used to login.
func WithHTTPClient(client *http.Client) LoginOption {
return func(lo *LoginOptions) {
lo.client = client
}
}
func applyLoginOptions(opts []LoginOption) *LoginOptions {
o := &LoginOptions{}
for _, opt := range opts {
opt(o)
}
return o
}
// memberLoginRegex is used to extract the next URL. No need for parsing HTML, just fetch the URL.
var memberLoginRegex = regexp.MustCompile(
`href="(http://live\.fc2\.com/member_login/\?uid=[^&]+&cc=[^"]+)"`,
)
var usernameRegex = regexp.MustCompile(`<span class="m-hder01_uName">(.*?)</span>`)
// Login to FC2 and fill the CookieJar.
//
// You need to probably need to
func Login(ctx context.Context, opts ...LoginOption) error {
o := applyLoginOptions(opts)
client := http.DefaultClient
if o.client != nil {
client = o.client
}
// Phase 1: Redirect to https://id.fc2.com
var memberLoginURL string
if err := func() error {
req, err := http.NewRequestWithContext(ctx, "GET", "https://live.fc2.com/login/", nil)
if err != nil {
return err
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("non-ok http code returned: %d", resp.StatusCode)
}
if resp.Request.URL.Host != "id.fc2.com" {
return fmt.Errorf("reached unknown location: %s", resp.Request.Host)
}
// Look for next URL
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
matches := memberLoginRegex.FindStringSubmatch(string(body))
if len(matches) == 0 {
return errors.New("failed to find next url, cookies are invalid")
}
memberLoginURL = matches[1]
log.Info().Str("memberLoginURL", memberLoginURL).Msg("login phase 1 success")
return nil
}(); err != nil {
return fmt.Errorf("login phase 1 failed: %w", err)
}
// Phase 2: Login to https://live.fc2.com/member_login/?uid=<uid>&cc=<cc>
if err := func() error {
u, err := url.Parse(memberLoginURL)
if err != nil {
return err
}
u.Scheme = "https"
req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
if err != nil {
return err
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("non-ok http code returned: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if !strings.Contains(string(body), "logout") {
return errors.New("failed to find 'logout', which means the login failed")
}
matches := usernameRegex.FindStringSubmatch(string(body))
if len(matches) == 0 {
log.Info().Msg("login phase 2 success (but we didn't find your username)")
} else {
log.Info().Str("username", matches[1]).Msg("login phase 2 success")
}
return nil
}(); err != nil {
return fmt.Errorf("login phase 2 failed: %w", err)
}
return nil
}
// LoginLoop will try to login to FC2 every duration.
func LoginLoop(
ctx context.Context,
duration time.Duration,
opts ...LoginOption,
) {
ticker := time.NewTicker(duration)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := Login(ctx, opts...); err != nil {
if err := notifier.NotifyLoginFailed(ctx, err); err != nil {
log.Err(err).Msg("notify failed")
}
log.Err(err).
Msg("failed to login to id.fc2.com, we will try again, but you should extract new cookies")
}
case <-ctx.Done():
return
}
}
}