如何在Flutter项目中使用和验证Last.fm API

ruarlubt  于 2023-06-30  发布在  Flutter
关注(0)|答案(1)|浏览(137)

我是Flutter和使用API的新手。我正在尝试做一个非常简单的Flutter应用程序,它会向用户推荐与他们使用www.example.com API输入的音乐曲目相似的曲目Last.fm。我已经有了一个具有API密钥和共享密钥的帐户。但是,我不确定如何实现Last.fm API。每次我尝试获得推荐时,什么都没有发生,我收到错误消息:"flutter: Failed to authenticate or get similar tracks: Connection failed."我知道它与URL有关,因为当我单击'http://ws.audioscrobbler.com/2.0/'链接时,文档树看起来是这样的:

<lfm status="failed">
<style class="darkreader darkreader--safari-fallback">
html, body, body > :not(iframe) { background-color: #181a1b !important; border-color: #776e62 !important; color: #e8e6e3 !important; }
</style>
<error code="6">
Invalid parameters - Your request is missing a required parameter
</error>
</lfm>

下面是主要的.dart代码:

import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(MyApp());
}

class LastfmApi {
  final String baseUrl = 'http://ws.audioscrobbler.com/2.0/';
  final String apiKey = '31010505f53e40c5c06cbf0941e5f380';
  final String secret = 'f0288f77e13a6c6fa1d5a5622a1a6f79';
  String? sessionKey;

  String _generateSignature(Map<String, String> params) {
    final sortedParams = Map.fromEntries(
        params.entries.toList()..sort((a, b) => a.key.compareTo(b.key)));
    final paramStrings =
        sortedParams.entries.map((e) => '${e.key}${e.value}').join('');
    final signature = utf8.encode(secret + paramStrings);
    return md5.convert(signature).toString();
  }

  Future<void> authenticate(String username, String password) async {
    final timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).round();
    final params = {
      'api_key': apiKey,
      'method': 'auth.getMobileSession',
      'password': password,
      'username': username,
      'timestamp': timestamp.toString(),
    };
    params['api_sig'] = _generateSignature(params);

    final response = await http.post(Uri.parse(baseUrl), body: params);

    if (response.statusCode == 200) {
      final jsonBody = jsonDecode(response.body);
      final session = jsonBody['session'];
      if (session != null) {
        sessionKey = session['key'];
      } else {
        throw Exception('Failed to authenticate');
      }
    } else {
      throw Exception('Failed to authenticate');
    }
  }

  Future<List<String>> getSimilarTracks(String track, String artist) async {
    if (sessionKey == null) {
      throw Exception('Not authenticated');
    }

    final timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).round();
    final params = {
      'api_key': apiKey,
      'format': 'json',
      'limit': '10',
      'sk': sessionKey!,
      'method': 'track.getsimilar',
      'timestamp': timestamp.toString(),
      'track': track,
      'artist': artist,
      'autocorrect': '1',
    };
    params['api_sig'] = _generateSignature(params);

    final response = await http.get(Uri.parse(baseUrl +
        '?' +
        params.entries
            .map((e) => '${e.key}=${Uri.encodeComponent(e.value)}')
            .join('&')));

    if (response.statusCode == 200) {
      final jsonBody = jsonDecode(response.body);
      if (jsonBody.containsKey('error')) {
        throw Exception(jsonBody['message']);
      }
      final tracks = jsonBody['similartracks']['track'] as List<dynamic>;
      final trackNames =
          tracks.map((track) => track['name'] as String).toList();
      return trackNames;
    } else {
      throw Exception('Failed to get similar tracks');
    }
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Last.fm Music Recommendations',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        brightness: Brightness.light,
      ),
      darkTheme: ThemeData(
        primarySwatch: Colors.blue,
        brightness: Brightness.dark,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  final TextEditingController _trackController = TextEditingController();
  final TextEditingController _usernameController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();
  final LastfmApi _lastfmApi = LastfmApi();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Title for recommendation'),
      ),
      body: Column(
        children: [
          TextField(
            controller: _trackController,
            decoration: InputDecoration(
              labelText: 'Enter a track name',
            ),
          ),
          TextField(
            controller: _usernameController,
            decoration: InputDecoration(
              labelText: 'Enter your Last.fm username',
            ),
          ),
          TextField(
            controller: _passwordController,
            obscureText: true,
            decoration: InputDecoration(
              labelText: 'Enter your Last.fm password',
            ),
          ),
          ElevatedButton(
            onPressed: () async {
              try {
                await _lastfmApi.authenticate(
                    _usernameController.text, _passwordController.text);
                final recommendations = await _lastfmApi.getSimilarTracks(
                    _trackController.text, '');
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => RecommendationPage(
                        track: _trackController.text,
                        recommendations: recommendations),
                  ),
                );
              } catch (e) {
                print('Failed to authenticate or get similar tracks: $e');
              }
            },
            child: Text('Get recommendations'),
          ),
        ],
      ),
    );
  }
}

class RecommendationPage extends StatefulWidget {
  final String track;
  final List<dynamic> recommendations;

  RecommendationPage({required this.track, required this.recommendations});

  @override
  _RecommendationPageState createState() => _RecommendationPageState();
}

class _RecommendationPageState extends State<RecommendationPage> {
  @override
  void initState() {
    super.initState();
  }

/*
  Future<void> _loadRecommendations() async {
    try {
      final recommendations = await getSimilarTracks(widget.track, '');
      setState(() {
        _recommendations = recommendations;
      });
    } catch (e) {
      throw Exception('Failed to load recommendations');
    }
  }
*/
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Recommendations for ${widget.track}'),
      ),
      body: ListView.builder(
        itemCount: widget.recommendations.length,
        itemBuilder: (BuildContext context, int index) {
          final recommendation = widget.recommendations[index];

          return ListTile(
            leading: Image.network(recommendation['image'][2]['#text']),
            title: Text(recommendation['name']),
            subtitle: Text(recommendation['artist']['name']),
          );
        },
      ),
    );
  }
}

我知道说明在这个链接:https://www.last.fm/api/mobileauth,但我对如何实现它感到困惑。
我尝试更改代码,以便它需要last.fm用户名和密码,并使用三个TextEditingController对象_trackController、_usernameController和_passwordController,它们用于捕获用户输入的曲目名称、用户名和密码。我以为它会验证Last.fm API并获得建议。但是,当我点击“获取建议”按钮时,什么也没有发生。

bprjcwpo

bprjcwpo1#

我还没有检查你所有的代码,但我已经看到了一些部分,应该值得尝试以另一种方式做,根据文档。
1.你有一个API的基本URL,但是当你调用某个方法时,你也需要将该路径附加到URL中。例如:http://ws.audioscrobbler.com/2.0/track.getSimilar
1.端点方法区分大小写,例如track.getsimilar!= track.getSimilar
1.请注意,为了验证移动的会话,您需要使用API url的https变体,如in the documentation所述。这是因为您通过线路发送用户的密码。
1.据我所知,您不需要timestamp作为身份验证请求中的参数。
1.在生成api_sig_generateSignature)的方法中,您将secret作为字符串的前缀,但应该将其追加:final signature = utf8.encode(paramStrings + secret);
以下是一些改进代码的提示。让我知道这是否有帮助,或者是否出现了新的问题。

相关问题