diff --git a/README.md b/README.md index c2f6ef5..19331e2 100644 --- a/README.md +++ b/README.md @@ -36,32 +36,14 @@ fmt.Printf("%v", stations) ### ■ Get & Set authentication token -- swftools is required. - ```go -// 1. Download a swf player. -swfPath := path.Join(dir, "myplayer.swf") -if err := radiko.DownloadPlayer(swfPath); err != nil { - log.Fatalf("Failed to download swf player. %s", err) -} - -// 2. Using swfextract, create an authkey file from a swf player. -cmdPath, err := exec.LookPath("swfextract") -if err != nil { - log.Fatal(err) -} -authKeyPath := path.Join(dir, "authkey.png") -if c := exec.Command(cmdPath, "-b", "12", swfPath, "-o", authKeyPath); err != c.Run() { - log.Fatalf("Failed to execute swfextract. %s", err) -} - -// 3. Create a new Client. +// 1. Create a new Client. client, err := radiko.New("") if err != nil { panic(err) } -// 4. Enables and sets the auth_token. +// 2. Enables and sets the auth_token. // After client.AuthorizeToken() has succeeded, // the client has the enabled auth_token internally. authToken, err := client.AuthorizeToken(context.Background(), authKeyPath) @@ -72,16 +54,14 @@ if err != nil { #### Premium member (Enable to use the [area free](http://radiko.jp/rg/premium/).) -Step 1,2 are the same as above. - ```go -// 3. Create a new Client. +// 1. Create a new Client. client, err := radiko.New("") if err != nil { panic(err) } -// 4. Login as the premium member +// 2. Login as the premium member // After client.Login() has succeeded, // the client has the valid cookie internally. ctx := context.Background() @@ -94,7 +74,7 @@ if login.StatusCode() != 200 { login.StatusCode()) } -// 5. Enables and sets the auth_token. +// 3. Enables and sets the auth_token. // After client.AuthorizeToken() has succeeded, // the client has the enabled auth_token internally. authToken, err := client.AuthorizeToken(context.Background(), authKeyPath) diff --git a/auth.go b/auth.go index 18207b7..21ee5cf 100644 --- a/auth.go +++ b/auth.go @@ -1,12 +1,13 @@ package radiko import ( + "bytes" "context" "encoding/base64" "errors" "fmt" + "io" "io/ioutil" - "os" "strconv" "strings" ) @@ -14,19 +15,22 @@ import ( // AuthorizeToken returns an enables auth_token and error, // and sets auth_token in Client. // Is is a alias function that wraps Auth1Fms and Auth2Fms. -func (c *Client) AuthorizeToken(ctx context.Context, pngFile string) (string, error) { - authToken, length, offset, err := c.Auth1Fms(ctx) +func (c *Client) AuthorizeToken(ctx context.Context) (string, error) { + bin, err := downloadBinary() if err != nil { return "", err } - f, err := os.Open(pngFile) + f := bytes.NewReader(bin) + + authToken, length, offset, err := c.Auth1Fms(ctx) if err != nil { return "", err } b := make([]byte, length) - if _, err = f.ReadAt(b, offset); err != nil { + io.CopyN(ioutil.Discard, f, offset) + if _, err = f.Read(b); err != nil { return "", err } partialKey := base64.StdEncoding.EncodeToString(b) diff --git a/auth_test.go b/auth_test.go index af8240b..a94401c 100644 --- a/auth_test.go +++ b/auth_test.go @@ -19,8 +19,7 @@ func TestAuthorizeToken(t *testing.T) { } ctx := context.Background() - pngPath := path.Join(testdataDir, "authkey.png") - authToken, err := c.AuthorizeToken(ctx, pngPath) + authToken, err := c.AuthorizeToken(ctx) if err != nil { t.Error(err) } @@ -29,20 +28,6 @@ func TestAuthorizeToken(t *testing.T) { } } -func TestAuthorizeToken_NotExistAuthkeyFile(t *testing.T) { - c, err := New("") - if err != nil { - t.Fatalf("Failed to construct client: %s", err) - } - - ctx := context.Background() - pngFile := path.Join(testdataDir, "not_exist_authkey.png") - _, err = c.AuthorizeToken(ctx, pngFile) - if err == nil { - t.Error(err) - } -} - func TestAuth1Fms(t *testing.T) { c, err := New("") if err != nil { diff --git a/examples/auth/main.go b/examples/auth/main.go index fa84a7b..ac1b924 100644 --- a/examples/auth/main.go +++ b/examples/auth/main.go @@ -6,42 +6,21 @@ import ( "io/ioutil" "log" "os" - "os/exec" - "path" radiko "github.com/yyoshiki41/go-radiko" ) func main() { - dir, f := createTempDir() - defer f() - - // 1. Download a swf player - swfPath := path.Join(dir, "myplayer.swf") - if err := radiko.DownloadPlayer(swfPath); err != nil { - log.Fatalf("Failed to download swf player. %s", err) - } - - // 2. Using swfextract, create an authkey file from a swf player. - cmdPath, err := exec.LookPath("swfextract") - if err != nil { - log.Fatal(err) - } - authKeyPath := path.Join(dir, "authkey.png") - if c := exec.Command(cmdPath, "-b", "12", swfPath, "-o", authKeyPath); err != c.Run() { - log.Fatalf("Failed to execute swfextract. %s", err) - } - - // 3. Create a new Client. + // 1. Create a new Client. client, err := radiko.New("") if err != nil { log.Fatalf("Failed to construct a radiko Client. %s", err) } - // 4. Enables and sets the auth_token. + // 2. Enables and sets the auth_token. // After client.AuthorizeToken() has succeeded, // the client has the enabled auth_token internally. - authToken, err := client.AuthorizeToken(context.Background(), authKeyPath) + authToken, err := client.AuthorizeToken(context.Background()) if err != nil { log.Fatal(err) } diff --git a/player.go b/player.go index 8d9ad55..b9abc58 100644 --- a/player.go +++ b/player.go @@ -1,31 +1,92 @@ package radiko import ( + "compress/zlib" + "errors" "io" + "io/ioutil" "net/http" - "os" ) const ( playerURL = "http://radiko.jp/apps/js/flash/myplayer-release.swf" ) -// DownloadPlayer downloads a swf player file. -func DownloadPlayer(path string) error { +func downloadBinary() ([]byte, error) { resp, err := http.Get(playerURL) if err != nil { - return err + return nil, err } defer resp.Body.Close() - f, err := os.Create(path) + return swfExtract(resp.Body) +} + +const targetID = 12 // swfextract -b "12" +const targetCode = 87 // swfextract "-b" 12 + +const headerCWS = 8 +const headerRect = 5 +const rectNum = 4 +const headerRest = 2 + 2 +const binaryOffset = 6 + +func swfExtract(body io.Reader) ([]byte, error) { + io.CopyN(ioutil.Discard, body, headerCWS) + zf, err := zlib.NewReader(body) if err != nil { - return err + return nil, err } + buf, err := ioutil.ReadAll(zf) + if err != nil { + return nil, err + } + + offset := 0 + + // Skip Rect + rectSize := int(buf[offset] >> 3) + rectOffset := (headerRect + rectNum*rectSize + 7) / 8 + + offset += rectOffset + + // Skip the rest header + offset += headerRest + + // Read tags + for i := 0; ; i++ { + // tag code + code := int(buf[offset+1])<<2 + int(buf[offset])>>6 + + // tag length + len := int(buf[offset] & 0x3f) + + // Skip tag header + offset += 2 + + // tag length (if long version) + if len == 0x3f { + len = int(buf[offset]) + len += int(buf[offset+1]) << 8 + len += int(buf[offset+2]) << 16 + len += int(buf[offset+3]) << 24 + + // skip tag lentgh header + offset += 4 + } + + // Not found... + if code == 0 { + return nil, errors.New("swf extract failed") + } + // tag ID + id := int(buf[offset]) + int(buf[offset+1])<<8 + + // Found? + if code == targetCode && id == targetID { + return buf[offset+binaryOffset : offset+len], nil + } - _, err = io.Copy(f, resp.Body) - if closeErr := f.Close(); err == nil { - err = closeErr + offset += len } - return err } diff --git a/player_test.go b/player_test.go index a0b44b3..4452fd9 100644 --- a/player_test.go +++ b/player_test.go @@ -1,17 +1,10 @@ package radiko -import ( - "path" - "testing" -) +import "testing" -func TestDownloadPlayer(t *testing.T) { - dir, removeDir := createTestTempDir(t) - defer removeDir() // clean up - - playerPath := path.Join(dir, "myplayer.swf") - err := DownloadPlayer(playerPath) +func TestDownloadBinary(t *testing.T) { + _, err := downloadBinary() if err != nil { - t.Errorf("Failed to download player.swf: %s", err) + t.Errorf("Failed to download binary: %s", err) } }