add lyrics screen
This commit is contained in:
43
configure.in
43
configure.in
@@ -11,28 +11,25 @@ AM_PROG_LIBTOOL
|
||||
|
||||
AC_ARG_ENABLE(unicode, AS_HELP_STRING([--enable-unicode], [Enable utf8 support]), [unicode=$enableval], [unicode=yes])
|
||||
AC_ARG_WITH(taglib, AS_HELP_STRING([--with-taglib], [Enable tag editor]), [taglib=$withval], [taglib=no])
|
||||
AC_ARG_WITH(curl, AS_HELP_STRING([--with-curl], [Enable fetching lyrics from the Internet]), [curl=$withval], [curl=no])
|
||||
|
||||
dnl ========================
|
||||
dnl = checking for ncurses =
|
||||
dnl ========================
|
||||
if test "$unicode" = "yes" ; then
|
||||
AC_PATH_PROG(NCURSESW5_CONFIG, ncursesw5-config)
|
||||
if test "$NCURSESW5_CONFIG" != "" ; then
|
||||
CPPFLAGS="$CPPFLAGS -DUTF8_ENABLED `$NCURSESW5_CONFIG --cflags`"
|
||||
LDFLAGS="$LDFLAGS `$NCURSESW5_CONFIG --libs`"
|
||||
AC_CHECK_LIB(ncursesw, initscr, , AC_MSG_ERROR([ncursesw library is required]))
|
||||
else
|
||||
AC_MSG_ERROR([ncursesw5-config executable is missing])
|
||||
fi
|
||||
ncurses_config_bin=ncursesw5-config
|
||||
ncurses_lib=ncursesw
|
||||
else
|
||||
AC_PATH_PROG(NCURSES5_CONFIG, ncurses5-config)
|
||||
if test "$NCURSES5_CONFIG" != "" ; then
|
||||
CPPFLAGS="$CPPFLAGS `$NCURSES5_CONFIG --cflags`"
|
||||
LDFLAGS="$LDFLAGS `$NCURSES5_CONFIG --libs`"
|
||||
AC_CHECK_LIB(ncurses, initscr, , AC_MSG_ERROR([ncurses library is required]))
|
||||
else
|
||||
AC_MSG_ERROR([ncurses5-config executable is missing])
|
||||
fi
|
||||
ncurses_config_bin=ncurses5-config
|
||||
ncurses_lib=ncurses
|
||||
fi
|
||||
AC_PATH_PROG(NCURSES_CONFIG, $ncurses_config_bin)
|
||||
if test "$NCURSES_CONFIG" != "" ; then
|
||||
CPPFLAGS="$CPPFLAGS -DUTF8_ENABLED `$NCURSES_CONFIG --cflags`"
|
||||
LDFLAGS="$LDFLAGS `$NCURSES_CONFIG --libs`"
|
||||
AC_CHECK_LIB($ncurses_lib, initscr, , AC_MSG_ERROR([$ncurses_lib library is required]))
|
||||
else
|
||||
AC_MSG_ERROR([$ncurses_config_bin executable is missing])
|
||||
fi
|
||||
AC_CHECK_HEADERS([ncurses.h], , AC_MSG_ERROR([missing ncurses.h header]))
|
||||
|
||||
@@ -46,6 +43,20 @@ CPPFLAGS="$CPPFLAGS $libmpd_CFLAGS"
|
||||
LDFLAGS="$LDFLAGS $libmpd_LIBS"
|
||||
AC_CHECK_HEADERS([libmpd/libmpd.h], , AC_MSG_ERROR([missing libmpd.h header]))
|
||||
|
||||
dnl =====================
|
||||
dnl = checking for curl =
|
||||
dnl =====================
|
||||
if test "$curl" = "yes" ; then
|
||||
AC_PATH_PROG(CURL_CONFIG, curl-config)
|
||||
if test "$CURL_CONFIG" != "" ; then
|
||||
CPPFLAGS="$CPPFLAGS `$CURL_CONFIG --cflags`"
|
||||
AC_CHECK_LIB(curl, curl_easy_init, LDFLAGS="$LDFLAGS `$CURL_CONFIG --libs`", AC_MSG_ERROR([curl library is required]))
|
||||
AC_CHECK_HEADERS([curl/curl.h], , AC_MSG_ERROR([missing curl.h header]))
|
||||
else
|
||||
AC_MSG_ERROR([curl-config executable is missing])
|
||||
fi
|
||||
fi
|
||||
|
||||
dnl =======================
|
||||
dnl = checking for taglib =
|
||||
dnl =======================
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
bin_PROGRAMS = ncmpcpp
|
||||
ncmpcpp_SOURCES = color_parser.cpp helpers.cpp menu.cpp misc.cpp ncmpcpp.cpp \
|
||||
scrollpad.cpp settings.cpp song.cpp status_checker.cpp window.cpp
|
||||
ncmpcpp_SOURCES = color_parser.cpp helpers.cpp lyrics.cpp menu.cpp misc.cpp \
|
||||
ncmpcpp.cpp scrollpad.cpp settings.cpp song.cpp status_checker.cpp window.cpp
|
||||
|
||||
# set the include path found by configure
|
||||
INCLUDES= $(all_includes)
|
||||
|
||||
# the library search path.
|
||||
ncmpcpp_LDFLAGS = $(all_libraries)
|
||||
noinst_HEADERS = helpers.h menu.h scrollpad.h settings.h song.h \
|
||||
noinst_HEADERS = helpers.h lyrics.h menu.h scrollpad.h settings.h song.h \
|
||||
status_checker.h window.h
|
||||
|
||||
@@ -37,7 +37,7 @@ extern Window *wFooter;
|
||||
extern vector<Song *> vPlaylist;
|
||||
extern vector<BrowsedItem> vBrowser;
|
||||
|
||||
extern CurrScreen current_screen;
|
||||
extern NcmpcppScreen current_screen;
|
||||
|
||||
extern Song edited_song;
|
||||
extern Song searched_song;
|
||||
|
||||
107
src/lyrics.cpp
Normal file
107
src/lyrics.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* 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 2 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, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include "lyrics.h"
|
||||
|
||||
const string lyrics_folder = home_folder + "/" + ".lyrics";
|
||||
const string mkdir_command = "mkdir " + lyrics_folder + " &>/dev/null";
|
||||
|
||||
size_t write_data(char *buffer, size_t size, size_t nmemb, string data)
|
||||
{
|
||||
int result = 0;
|
||||
if (buffer)
|
||||
{
|
||||
data += buffer;
|
||||
result = size*nmemb;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
string GetLyrics(string artist, string song)
|
||||
{
|
||||
const string filename = artist + " - " + song + ".txt";
|
||||
const string fullpath = lyrics_folder + "/" + filename;
|
||||
system(mkdir_command.c_str());
|
||||
|
||||
string result;
|
||||
std::ifstream input(fullpath.c_str());
|
||||
|
||||
if (input.is_open())
|
||||
{
|
||||
string line;
|
||||
while (getline(input, line))
|
||||
result += line + "\n";
|
||||
return result;
|
||||
}
|
||||
|
||||
# ifdef HAVE_CURL_CURL_H
|
||||
for (string::iterator it = artist.begin(); it != artist.end(); it++)
|
||||
if (*it == ' ')
|
||||
*it = '+';
|
||||
|
||||
for (string::iterator it = song.begin(); it != song.end(); it++)
|
||||
if (*it == ' ')
|
||||
*it = '+';
|
||||
|
||||
CURLcode code;
|
||||
|
||||
string url = "http://lyricwiki.org/api.php?artist=" + artist + "&song=" + song + "&fmt=xml";
|
||||
|
||||
CURL *lyrics = curl_easy_init();
|
||||
curl_easy_setopt(lyrics, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(lyrics, CURLOPT_WRITEFUNCTION, write_data);
|
||||
curl_easy_setopt(lyrics, CURLOPT_WRITEDATA, &result);
|
||||
curl_easy_setopt(lyrics, CURLOPT_CONNECTTIMEOUT, 30);
|
||||
code = curl_easy_perform(lyrics);
|
||||
curl_easy_cleanup(lyrics);
|
||||
|
||||
if (code != CURLE_OK)
|
||||
{
|
||||
result = "Error while fetching lyrics: " + string(curl_easy_strerror(code));
|
||||
return result;
|
||||
}
|
||||
|
||||
int a, b;
|
||||
a = result.find("<lyrics>")+8;
|
||||
b = result.find("</lyrics>");
|
||||
|
||||
result = result.substr(a, b-a);
|
||||
|
||||
if (result == "Not found")
|
||||
return result;
|
||||
|
||||
for (int i = result.find("'"); i != string::npos; i = result.find("'"))
|
||||
result.replace(i, 6, "'");
|
||||
for (int i = result.find("""); i != string::npos; i = result.find("""))
|
||||
result.replace(i, 6, "\"");
|
||||
|
||||
std::ofstream output(fullpath.c_str());
|
||||
|
||||
if (output.is_open())
|
||||
{
|
||||
output << result;
|
||||
output.close();
|
||||
}
|
||||
# else
|
||||
result = "Local lyrics not found. As ncmpcpp has been compiled without curl support, you can put appropriate lyrics into ~/.lyrics directory (file syntax is \"ARTIST - TITLE.txt\") or recompile ncmpcpp with curl support.";
|
||||
# endif
|
||||
return result + '\n';
|
||||
}
|
||||
|
||||
34
src/lyrics.h
Normal file
34
src/lyrics.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2008 by Andrzej Rybczak *
|
||||
* electricityispower@gmail.com *
|
||||
* *
|
||||
* 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 2 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, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef HAVE_LYRICS_H
|
||||
#define HAVE_LYRICS_H
|
||||
|
||||
#include "ncmpcpp.h"
|
||||
|
||||
#include <fstream>
|
||||
#ifdef HAVE_CURL_CURL_H
|
||||
# include <curl/curl.h>
|
||||
#endif
|
||||
|
||||
string GetLyrics(string, string);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "helpers.h"
|
||||
#include "settings.h"
|
||||
#include "song.h"
|
||||
#include "lyrics.h"
|
||||
|
||||
#define FOR_EACH_MPD_DATA(x) for (; (x); (x) = mpd_data_get_next(x))
|
||||
|
||||
@@ -43,6 +44,13 @@
|
||||
block_progressbar_update = 0; \
|
||||
}
|
||||
|
||||
#define REFRESH_MEDIA_LIBRARY_SCREEN \
|
||||
mLibArtists->Display(redraw_me); \
|
||||
mvvline(main_start_y, lib_albums_start_x-1, 0, main_height); \
|
||||
mLibAlbums->Display(redraw_me); \
|
||||
mvvline(main_start_y, lib_songs_start_x-1, 0, main_height); \
|
||||
mLibSongs->Display(redraw_me)
|
||||
|
||||
#ifdef HAVE_TAGLIB_H
|
||||
const string tag_screen = "Tag editor";
|
||||
const string tag_screen_keydesc = "\tE e : Edit song's tags\n";
|
||||
@@ -77,6 +85,7 @@ Menu *mLibArtists;
|
||||
Menu *mLibAlbums;
|
||||
Menu *mLibSongs;
|
||||
Scrollpad *sHelp;
|
||||
Scrollpad *sLyrics;
|
||||
|
||||
Window *wHeader;
|
||||
Window *wFooter;
|
||||
@@ -107,8 +116,8 @@ string mpd_random;
|
||||
string mpd_crossfade;
|
||||
string mpd_db_updating;
|
||||
|
||||
CurrScreen current_screen;
|
||||
CurrScreen prev_screen;
|
||||
NcmpcppScreen current_screen;
|
||||
NcmpcppScreen prev_screen;
|
||||
|
||||
Song edited_song;
|
||||
Song searched_song;
|
||||
@@ -189,6 +198,7 @@ int main(int argc, char *argv[])
|
||||
mLibSongs = new Menu(lib_songs_start_x, main_start_y, lib_songs_width, main_height, "Songs", Config.main_color, brNone);
|
||||
|
||||
sHelp = new Scrollpad(0, main_start_y, COLS, main_height, "", Config.main_color, brNone);
|
||||
sLyrics = new Scrollpad(sHelp->EmptyClone());
|
||||
|
||||
sHelp->Add(" [b]Keys - Movement\n -----------------------------------------[/b]\n");
|
||||
sHelp->Add("\tUp : Move Cursor up\n");
|
||||
@@ -224,7 +234,8 @@ int main(int argc, char *argv[])
|
||||
sHelp->Add("\t? : Backward find\n");
|
||||
sHelp->Add("\t. : Go to next/previous found position\n");
|
||||
sHelp->Add(tag_screen_keydesc);
|
||||
sHelp->Add("\tg : Go to chosen position in current song\n\n");
|
||||
sHelp->Add("\tg : Go to chosen position in current song\n");
|
||||
sHelp->Add("\tl : Show/hide song's lyrics\n\n");
|
||||
|
||||
sHelp->Add("\tQ q : Quit\n\n\n");
|
||||
|
||||
@@ -292,6 +303,7 @@ int main(int argc, char *argv[])
|
||||
mLibArtists->Timeout(ncmpcpp_window_timeout);
|
||||
mLibAlbums->Timeout(ncmpcpp_window_timeout);
|
||||
mLibSongs->Timeout(ncmpcpp_window_timeout);
|
||||
sLyrics->Timeout(ncmpcpp_window_timeout);
|
||||
wFooter->Timeout(ncmpcpp_window_timeout);
|
||||
|
||||
while (!main_exit)
|
||||
@@ -326,6 +338,9 @@ int main(int argc, char *argv[])
|
||||
case csLibrary:
|
||||
title = "Media library";
|
||||
break;
|
||||
case csLyrics:
|
||||
title = "Lyrics";
|
||||
break;
|
||||
}
|
||||
|
||||
if (title_allowed)
|
||||
@@ -523,6 +538,8 @@ int main(int argc, char *argv[])
|
||||
mBrowser->Resize(COLS, main_height);
|
||||
mTagEditor->Resize(COLS, main_height);
|
||||
mSearcher->Resize(COLS, main_height);
|
||||
sLyrics->Resize(COLS, main_height);
|
||||
sLyrics->Timeout(ncmpcpp_window_timeout);
|
||||
|
||||
lib_artist_width = COLS/3-1;
|
||||
lib_albums_start_x = lib_artist_width+1;
|
||||
@@ -550,13 +567,9 @@ int main(int argc, char *argv[])
|
||||
if (current_screen == csLibrary)
|
||||
{
|
||||
mLibArtists->Hide();
|
||||
mLibArtists->Display(redraw_me);
|
||||
mvvline(main_start_y, lib_albums_start_x-1, 0, main_height);
|
||||
mLibAlbums->Hide();
|
||||
mLibAlbums->Display(redraw_me);
|
||||
mvvline(main_start_y, lib_songs_start_x-1, 0, main_height);
|
||||
mLibSongs->Hide();
|
||||
mLibSongs->Display(redraw_me);
|
||||
REFRESH_MEDIA_LIBRARY_SCREEN;
|
||||
}
|
||||
|
||||
header_update_status = 1;
|
||||
@@ -786,11 +799,7 @@ int main(int argc, char *argv[])
|
||||
# else
|
||||
wCurrent = wPrev;
|
||||
# endif
|
||||
mLibArtists->Display(redraw_me);
|
||||
mvvline(main_start_y, lib_albums_start_x-1, 0, main_height);
|
||||
mLibAlbums->Display(redraw_me);
|
||||
mvvline(main_start_y, lib_songs_start_x-1, 0, main_height);
|
||||
mLibSongs->Display(redraw_me);
|
||||
REFRESH_MEDIA_LIBRARY_SCREEN;
|
||||
}
|
||||
# ifdef HAVE_TAGLIB_H
|
||||
break;
|
||||
@@ -1599,6 +1608,60 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'l': // show lyrics
|
||||
{
|
||||
if (wCurrent == sLyrics)
|
||||
{
|
||||
wCurrent->Hide();
|
||||
current_screen = prev_screen;
|
||||
wCurrent = wPrev;
|
||||
redraw_me = 1;
|
||||
if (current_screen == csLibrary)
|
||||
{
|
||||
REFRESH_MEDIA_LIBRARY_SCREEN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ((wCurrent == mPlaylist && !vPlaylist.empty())
|
||||
|| (wCurrent == mBrowser && vBrowser[mBrowser->GetChoice()-1].type == MPD_DATA_TYPE_SONG)
|
||||
|| (wCurrent == mSearcher && !vSearched.empty() && mSearcher->GetChoice() > search_engine_static_option)
|
||||
|| (wCurrent == mLibSongs))
|
||||
{
|
||||
Song s;
|
||||
switch (current_screen)
|
||||
{
|
||||
case csPlaylist:
|
||||
s = *vPlaylist[mPlaylist->GetChoice()-1];
|
||||
break;
|
||||
case csBrowser:
|
||||
s = mpd_database_get_fileinfo(conn, vBrowser[mBrowser->GetChoice()-1].name.c_str());
|
||||
break;
|
||||
case csSearcher:
|
||||
s = vSearched[mSearcher->GetChoice()-search_engine_static_option-1];
|
||||
break;
|
||||
case csLibrary:
|
||||
s = vSongs[mLibSongs->GetChoice()-1];
|
||||
break;
|
||||
}
|
||||
|
||||
if (s.GetArtist() != UNKNOWN_ARTIST && s.GetTitle() != UNKNOWN_TITLE)
|
||||
{
|
||||
wPrev = wCurrent;
|
||||
prev_screen = current_screen;
|
||||
wCurrent = sLyrics;
|
||||
wCurrent->Hide();
|
||||
wCurrent->Clear();
|
||||
current_screen = csLyrics;
|
||||
|
||||
sLyrics->WriteXY(0, 0, "Fetching lyrics...");
|
||||
sLyrics->Refresh();
|
||||
sLyrics->Add("[b]" + s.GetArtist() + " - " + s.GetTitle() + "[/b]\n\n");
|
||||
sLyrics->Add(GetLyrics(s.GetArtist(), s.GetTitle()));
|
||||
sLyrics->Timeout(ncmpcpp_window_timeout);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '1': // help screen
|
||||
{
|
||||
if (wCurrent != sHelp)
|
||||
@@ -1606,7 +1669,6 @@ int main(int argc, char *argv[])
|
||||
wCurrent = sHelp;
|
||||
wCurrent->Hide();
|
||||
current_screen = csHelp;
|
||||
redraw_me = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1722,11 +1784,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
wCurrent->Hide();
|
||||
|
||||
mLibArtists->Display();
|
||||
mvvline(main_start_y, lib_albums_start_x-1, 0, main_height);
|
||||
mLibAlbums->Display();
|
||||
mvvline(main_start_y, lib_songs_start_x-1, 0, main_height);
|
||||
mLibSongs->Display();
|
||||
REFRESH_MEDIA_LIBRARY_SCREEN;
|
||||
|
||||
wCurrent = mLibArtists;
|
||||
current_screen = csLibrary;
|
||||
|
||||
@@ -55,11 +55,12 @@ const bool UNICODE = 0;
|
||||
#include "scrollpad.h"
|
||||
#include "misc.h"
|
||||
|
||||
enum CurrScreen { csHelp, csPlaylist, csBrowser, csTagEditor, csSearcher, csLibrary };
|
||||
enum NcmpcppScreen { csHelp, csPlaylist, csBrowser, csTagEditor, csSearcher, csLibrary, csLyrics };
|
||||
|
||||
const int ncmpcpp_window_timeout = 500;
|
||||
const int search_engine_static_option = 17;
|
||||
|
||||
const string home_folder = getenv("HOME");
|
||||
const string TERMINAL_TYPE = getenv("TERM");
|
||||
|
||||
const string search_mode_one = "Match if tag contains searched phrase";
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
void Scrollpad::Add(string str)
|
||||
{
|
||||
if (itsXPos > 0 && str[0] != ' ')
|
||||
if (itsXPos > 0 && (str[0] != ' ' || str[0] != '\n'))
|
||||
str = " " + str;
|
||||
|
||||
itsRawContent += str;
|
||||
@@ -39,8 +39,13 @@ void Scrollpad::Add(string str)
|
||||
int space_pos = 0;
|
||||
bool collect = 0;
|
||||
|
||||
for (size_t i = 0; i <= s.length(); i++, itsXPos++)
|
||||
for (size_t i = 0; i < s.length(); i++)
|
||||
{
|
||||
if (s[i] != '\t')
|
||||
itsXPos++;
|
||||
else
|
||||
itsXPos += 8;
|
||||
|
||||
if (BBEnabled)
|
||||
{
|
||||
if (s[i] == '[')
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
class Scrollpad: public Window
|
||||
{
|
||||
public:
|
||||
Scrollpad(int startx, int starty, int width, int height, string title, COLOR color, BORDER border) : Window(startx, starty, width, height, title, color, border), itsBeginning(0), itsRealHeight(1), itsXPos(0) { delwin(itsWindow); itsWindow = newpad(0,0); }
|
||||
Scrollpad(int startx, int starty, int width, int height, string title, COLOR color, BORDER border) : Window(startx, starty, width, height, title, color, border), itsBeginning(0), itsRealHeight(1), itsXPos(0) { delwin(itsWindow); itsWindow = newpad(itsHeight,itsWidth); }
|
||||
virtual ~Scrollpad() {}
|
||||
virtual void Add(string);
|
||||
virtual void Display(bool = 0);
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
|
||||
#include "ncmpcpp.h"
|
||||
|
||||
const string home_folder = getenv("HOME");
|
||||
|
||||
struct ncmpcpp_config
|
||||
{
|
||||
string mpd_music_dir;
|
||||
|
||||
@@ -65,7 +65,7 @@ extern string mpd_random;
|
||||
extern string mpd_crossfade;
|
||||
extern string mpd_db_updating;
|
||||
|
||||
extern CurrScreen current_screen;
|
||||
extern NcmpcppScreen current_screen;
|
||||
|
||||
extern bool header_update_status;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user