summaryrefslogblamecommitdiff
path: root/lib/plugins/last_fm.ex
blob: 4607cbeeebd1e6bc7c0b54033525b0303f7a76c4 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                




                


                                                                                              

     


                                              
                     


                             
                     
                                                          

     
                 
                                
                                                                                               
                                                                                    
                                                    
                                  

     
                                                                                                               
                                     
                                                                  
                                                                                                       


                     
                                                                                                        
                                                               









                                                                                                        
                            
                                                                 
              
       


                     
                                                                                                
                                                       


                     
                                                                                                            
                                                 


                     
                                                                                     

                                                                      
                                                    


                                                                               
                                                              






                                 







                                  
                                                       
                                             
 
                                                                                                                                                   




                  

                                                           
                     


                                 

                                            
                                    

                                             
                                                           




                                                                        




                                                            
              
                                      



                           
                                                       
                                                                                                                                                 

                                                                                    
                                                                                                             





                                                                                                     
                                                                                                                             
                                                       











                                                                                                                                                                                          
 

                                                                                                                          

     

                                                                                

     
                                                                        






                                      















                                                                                            


     
defmodule Nola.Plugins.LastFm do
  require Logger

  @moduledoc """
  # last.fm

  * **!lastfm|np `[nick|username]`**
  * **.lastfm|np**
  * **+lastfm, -lastfm `<username last.fm>; ?lastfm`** Configurer un nom d'utilisateur last.fm
  """

  @single_trigger ~w(lastfm np)
  @pubsub_topics ~w(trigger:lastfm trigger:np)

  defstruct dets: nil

  def irc_doc, do: @moduledoc

  def start_link() do
    GenServer.start_link(__MODULE__, [], name: __MODULE__)
  end

  def init([]) do
    regopts = [type: __MODULE__]
    for t <- @pubsub_topics, do: {:ok, _} = Registry.register(Nola.PubSub, t, type: __MODULE__)
    dets_filename = (Nola.data_path() <> "/" <> "lastfm.dets") |> String.to_charlist
    {:ok, dets} = :dets.open_file(dets_filename, [])
    {:ok, %__MODULE__{dets: dets}}
  end

  def handle_info({:irc, :trigger, "lastfm", message = %{trigger: %{type: :plus, args: [username]}}}, state) do
    username = String.strip(username)
    :ok = :dets.insert(state.dets, {message.account.id, username})
    message.replyfun.("#{message.sender.nick}: nom d'utilisateur last.fm configuré: \"#{username}\".")
    {:noreply, state}
  end

  def handle_info({:irc, :trigger, "lastfm", message = %{trigger: %{type: :minus, args: []}}}, state) do
    text = case :dets.lookup(state.dets, message.account.id) do
             [{_nick, _username}] ->
               :dets.delete(state.dets, message.account.id)
               message.replyfun.("#{message.sender.nick}: nom d'utilisateur last.fm enlevé.")
             _ -> nil
           end
    {:noreply, state}
  end

  def handle_info({:irc, :trigger, "lastfm", message = %{trigger: %{type: :query, args: []}}}, state) do
    text = case :dets.lookup(state.dets, message.account.id) do
      [{_nick, username}] ->
        message.replyfun.("#{message.sender.nick}: #{username}.")
      _ -> nil
    end
    {:noreply, state}
  end

  def handle_info({:irc, :trigger, _, message = %{trigger: %{type: :bang, args: []}}}, state) do
    irc_now_playing(message.account.id, message, state)
    {:noreply, state}
  end

  def handle_info({:irc, :trigger, _, message = %{trigger: %{type: :bang, args: [nick_or_user]}}}, state) do
    irc_now_playing(nick_or_user, message, state)
    {:noreply, state}
  end

  def handle_info({:irc, :trigger, _, message = %{trigger: %{type: :dot}}}, state) do
    members = IRC.Membership.members(message.network, message.channel)
    foldfun = fn({nick, user}, acc) -> [{nick,user}|acc] end
    usernames = :dets.foldl(foldfun, [], state.dets)
                |> Enum.uniq()
                |> Enum.filter(fn({acct,_}) -> Enum.member?(members, acct) end)
                |> Enum.map(fn({_, u}) -> u end)
    for u <- usernames, do: irc_now_playing(u, message, state)
    {:noreply, state}
  end

  def handle_info(info, state) do
    {:noreply, state}
  end

  def terminate(_reason, state) do
    if state.dets do
      :dets.sync(state.dets)
      :dets.close(state.dets)
    end
    :ok
  end

  defp irc_now_playing(nick_or_user, message, state) do
    nick_or_user = String.strip(nick_or_user)

    id_or_user = if account = Nola.Account.get(nick_or_user) || Nola.Account.find_always_by_nick(message.network, message.channel, nick_or_user) do
      account.id
    else
      nick_or_user
    end

    username = case :dets.lookup(state.dets, id_or_user) do
      [{_, username}] -> username
      _ -> id_or_user
    end

    case now_playing(username) do
      {:error, text} when is_binary(text) ->
        message.replyfun.(text)
      {:ok, map} when is_map(map) ->
        track = fetch_track(username, map)
        text = format_now_playing(map, track)
        user = if account = Nola.Account.get(id_or_user) do
          user = IRC.UserTrack.find_by_account(message.network, account)
          if(user, do: user.nick, else: account.name)
        else
          username
        end
        if user && text do
          message.replyfun.("#{user} #{text}")
        else
          message.replyfun.("#{username}: pas de résultat")
        end
      other ->
        message.replyfun.("erreur :(")
    end
  end

  defp now_playing(user) do
    api = Application.get_env(:nola, :lastfm)[:api_key]
    url = "http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&format=json&limit=1&extended=1" <> "&api_key=" <> api <> "&user="<> user
    case HTTPoison.get(url) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> Jason.decode(body)
      {:ok, %HTTPoison.Response{status_code: 404}} -> {:error, "last.fm: utilisateur \"#{user}\" inexistant"}
      {:ok, %HTTPoison.Response{status_code: code}} -> {:error, "last.fm: erreur #{to_string(code)}"}
      error ->
        Logger.error "Lastfm http error: #{inspect error}"
        :error
    end
  end
  defp fetch_track(user, %{"recenttracks" => %{"track" => [ t = %{"name" => name, "artist" => %{"name" => artist}} | _]}}) do
    api = Application.get_env(:nola, :lastfm)[:api_key]
    url = "http://ws.audioscrobbler.com/2.0/?method=track.getInfo&format=json" <> "&api_key=" <> api <> "&username="<> user <> "&artist="<>URI.encode(artist)<>"&track="<>URI.encode(name)
    case HTTPoison.get(url) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        case Jason.decode(body) do
          {:ok, body} -> body["track"] || %{}
          _ -> %{}
        end
      error ->
        Logger.error "Lastfm http error: #{inspect error}"
        :error
    end
  end

  defp format_now_playing(%{"recenttracks" => %{"track" => [track = %{"@attr" => %{"nowplaying" => "true"}} | _]}}, et) do
    format_track(true, track, et)
  end

  defp format_now_playing(%{"recenttracks" => %{"track" => [track | _]}}, et) do
    format_track(false, track, et)
  end

  defp format_now_playing(%{"error" => err, "message" => message}, _) do
    "last.fm error #{err}: #{message}"
  end

  defp format_now_playing(miss) do
    nil
  end

  defp format_track(np, track, extended) do
    artist = track["artist"]["name"]
    album = if track["album"]["#text"], do: " (" <> track["album"]["#text"] <> ")", else: ""
    name = track["name"] <> album
    action = if np, do: "écoute ", else: "a écouté"
    love = if track["loved"] != "0", do: "❤️"
    count = if x = extended["userplaycount"], do: "x#{x} #{love}"
    tags = (get_in(extended, ["toptags", "tag"]) || [])
    |> Enum.map(fn(tag) -> tag["name"] end)
    |> Enum.filter(& &1)
    |> Enum.join(", ")

    [action, artist, name, count, tags, track["url"]]
    |> Enum.filter(& &1)
    |> Enum.map(&String.trim(&1))
    |> Enum.join(" - ")
  end

end