├── .gitignore ├── README.md ├── pom.xml ├── src └── main │ ├── java │ └── me │ │ └── reply │ │ └── deemixbot │ │ ├── Main.java │ │ ├── api │ │ ├── Deezer.java │ │ └── json │ │ │ ├── Album.java │ │ │ ├── Artist.java │ │ │ ├── DeezerPlaylistSearchResult.java │ │ │ ├── DeezerQueryJson.java │ │ │ ├── LittleTrack.java │ │ │ ├── LittleTrackContainer.java │ │ │ ├── SearchResult.java │ │ │ ├── Track.java │ │ │ └── User.java │ │ ├── bot │ │ ├── Bot.java │ │ ├── CommandHandler.java │ │ ├── Config.java │ │ ├── DownloadJob.java │ │ └── JsonFetcher.java │ │ ├── playlistchecker │ │ ├── DeezerPlaylistChecker.java │ │ ├── PlaylistChecker.java │ │ └── SpotifyPlaylistChecker.java │ │ ├── users │ │ ├── DownloadFormat.java │ │ ├── DownloadMode.java │ │ ├── User.java │ │ └── UserManager.java │ │ └── utils │ │ ├── Curl.java │ │ ├── ReplyKeyboardBuilder.java │ │ ├── URLUTF8Encoder.java │ │ └── UpdateUserlistRunnable.java │ └── resources │ └── log4j.properties └── start.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea/** 3 | *.jar 4 | *.txt 5 | *.json 6 | deemix/** -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deemix Telegram Bot 2 | 3 | This bot takes care of downloading music from Deezer using [Deemix](https://notabug.org/RemixDev/deemix), the new solution that replaces the old deezloader remix, no longer supported. -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | me.reply.deemixbot 8 | DeemixBot 9 | 0.4.1 10 | 11 | 12 | 13 | 1.8 14 | 1.8 15 | UTF-8 16 | UTF-8 17 | 18 | 19 | 20 | 21 | jitpack.io 22 | https://jitpack.io 23 | 24 | 25 | 26 | 27 | 28 | org.telegram 29 | telegrambots 30 | 4.9.1 31 | 32 | 33 | com.google.code.gson 34 | gson 35 | 2.8.6 36 | 37 | 38 | org.slf4j 39 | slf4j-log4j12 40 | 2.0.0-alpha1 41 | 42 | 43 | org.slf4j 44 | slf4j-api 45 | 2.0.0-alpha1 46 | 47 | 48 | com.vdurmont 49 | emoji-java 50 | 5.1.1 51 | 52 | 53 | com.github.thelinmichael 54 | spotify-web-api-java 55 | 6.3.0 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-dependency-plugin 64 | 3.0.1 65 | 66 | 67 | copy-dependencies 68 | package 69 | copy-dependencies 70 | 71 | 72 | 73 | 74 | maven-assembly-plugin 75 | 76 | 77 | 78 | me.reply.deemixbot.Main 79 | 80 | 81 | 82 | jar-with-dependencies 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/Main.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot; 2 | 3 | import me.reply.deemixbot.bot.Bot; 4 | import me.reply.deemixbot.bot.Config; 5 | import me.reply.deemixbot.users.UserManager; 6 | import me.reply.deemixbot.utils.UpdateUserlistRunnable; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.telegram.telegrambots.ApiContextInitializer; 10 | import org.telegram.telegrambots.meta.TelegramBotsApi; 11 | import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException; 12 | 13 | import java.io.IOException; 14 | import java.util.concurrent.ExecutorService; 15 | import java.util.concurrent.Executors; 16 | 17 | public class Main { 18 | private static final Logger logger = LoggerFactory.getLogger(Main.class); 19 | public static void main(String[] args) { 20 | ApiContextInitializer.init(); 21 | TelegramBotsApi telegramBotsApi = new TelegramBotsApi(); 22 | Config c; 23 | try { 24 | c = Config.loadFromFile(); 25 | } catch (IOException e) { 26 | e.printStackTrace(); 27 | logger.error("Error during config loading: " + e.getMessage()); 28 | return; 29 | } 30 | UserManager userManager = new UserManager(); 31 | try { 32 | telegramBotsApi.registerBot(new Bot(c,userManager)); 33 | } catch (TelegramApiRequestException e) { 34 | e.printStackTrace(); 35 | logger.error(e.getMessage()); 36 | } 37 | ExecutorService service = Executors.newSingleThreadExecutor(); 38 | service.execute(new UpdateUserlistRunnable(c)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/api/Deezer.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.api; 2 | 3 | import com.google.gson.Gson; 4 | import me.reply.deemixbot.api.json.DeezerPlaylistSearchResult; 5 | import me.reply.deemixbot.api.json.DeezerQueryJson; 6 | import me.reply.deemixbot.utils.Curl; 7 | 8 | import java.net.MalformedURLException; 9 | 10 | public class Deezer { 11 | private final static String API_QUERY_PREFIX = "https://api.deezer.com/search?q="; 12 | private final static String API_PLAYLIST_PREFIX = "https://api.deezer.com/playlist/"; 13 | 14 | public static DeezerQueryJson getQuery(String query) throws MalformedURLException { 15 | Curl curl = new Curl(API_QUERY_PREFIX,query); 16 | String json = curl.run(); 17 | Gson g = new Gson(); 18 | return g.fromJson(json, DeezerQueryJson.class); 19 | } 20 | 21 | public static DeezerPlaylistSearchResult getPlaylist(String query) throws MalformedURLException { 22 | Curl curl = new Curl(API_PLAYLIST_PREFIX,query); 23 | String json = curl.run(); 24 | Gson g = new Gson(); 25 | return g.fromJson(json,DeezerPlaylistSearchResult.class); 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/api/json/Album.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.api.json; 2 | 3 | @SuppressWarnings("unused") 4 | public class Album{ 5 | private int id; 6 | private String title; 7 | private String cover; 8 | private String cover_small; 9 | private String cover_medium; 10 | private String cover_big; 11 | private String cover_xl; 12 | private String tracklist; 13 | private String type; 14 | 15 | public int getId() { 16 | return id; 17 | } 18 | 19 | public String getTitle() { 20 | return title; 21 | } 22 | 23 | public String getCover() { 24 | return cover; 25 | } 26 | 27 | public String getCover_small() { 28 | return cover_small; 29 | } 30 | 31 | public String getCover_medium() { 32 | return cover_medium; 33 | } 34 | 35 | public String getCover_big() { 36 | return cover_big; 37 | } 38 | 39 | public String getCover_xl() { 40 | return cover_xl; 41 | } 42 | 43 | public String getTracklist() { 44 | return tracklist; 45 | } 46 | 47 | public String getType() { 48 | return type; 49 | } 50 | 51 | public String getLink(){ 52 | return "https://deezer.com/album/" + id; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/api/json/Artist.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.api.json; 2 | 3 | @SuppressWarnings("unused") 4 | public class Artist{ 5 | private int id; 6 | private String name; 7 | private String link; 8 | private String picture; 9 | private String picture_small; 10 | private String picture_medium; 11 | private String picture_big; 12 | private String picture_xl; 13 | private String tracklist; 14 | private String type; 15 | 16 | public int getId() { 17 | return id; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public String getLink() { 25 | return link; 26 | } 27 | 28 | public String getPicture() { 29 | return picture; 30 | } 31 | 32 | public String getPicture_small() { 33 | return picture_small; 34 | } 35 | 36 | public String getPicture_medium() { 37 | return picture_medium; 38 | } 39 | 40 | public String getPicture_big() { 41 | return picture_big; 42 | } 43 | 44 | public String getPicture_xl() { 45 | return picture_xl; 46 | } 47 | 48 | public String getTracklist() { 49 | return tracklist; 50 | } 51 | 52 | public String getType() { 53 | return type; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/api/json/DeezerPlaylistSearchResult.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.api.json; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | public class DeezerPlaylistSearchResult { 6 | private long id; 7 | private String title; 8 | private String description; 9 | private int duration; 10 | @SerializedName("public") 11 | private boolean is_public; 12 | private boolean is_loved_track; 13 | private boolean collaborative; 14 | private int rating; 15 | private int nb_tracks; 16 | private int unseen_track_count; 17 | private int fans; 18 | private String link; 19 | private String share; 20 | private String picture; 21 | private String picture_small; 22 | private String picture_medium; 23 | private String picture_big; 24 | private String picture_xl; 25 | private String checksum; 26 | private User creator; 27 | private LittleTrackContainer tracks; 28 | 29 | 30 | public long getId() { 31 | return id; 32 | } 33 | 34 | public String getTitle() { 35 | return title; 36 | } 37 | 38 | public String getDescription() { 39 | return description; 40 | } 41 | 42 | public int getDuration() { 43 | return duration; 44 | } 45 | 46 | public boolean isIs_public() { 47 | return is_public; 48 | } 49 | 50 | public boolean isIs_loved_track() { 51 | return is_loved_track; 52 | } 53 | 54 | public boolean isCollaborative() { 55 | return collaborative; 56 | } 57 | 58 | public int getRating() { 59 | return rating; 60 | } 61 | 62 | public int getNb_tracks() { 63 | return nb_tracks; 64 | } 65 | 66 | public int getUnseen_track_count() { 67 | return unseen_track_count; 68 | } 69 | 70 | public int getFans() { 71 | return fans; 72 | } 73 | 74 | public String getLink() { 75 | return link; 76 | } 77 | 78 | public String getShare() { 79 | return share; 80 | } 81 | 82 | public String getPicture() { 83 | return picture; 84 | } 85 | 86 | public String getPicture_small() { 87 | return picture_small; 88 | } 89 | 90 | public String getPicture_medium() { 91 | return picture_medium; 92 | } 93 | 94 | public String getPicture_big() { 95 | return picture_big; 96 | } 97 | 98 | public String getPicture_xl() { 99 | return picture_xl; 100 | } 101 | 102 | public String getChecksum() { 103 | return checksum; 104 | } 105 | 106 | public User getCreator() { 107 | return creator; 108 | } 109 | 110 | public LittleTrackContainer getTracks() { 111 | return tracks; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/api/json/DeezerQueryJson.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.api.json; 2 | 3 | @SuppressWarnings("unused") 4 | public class DeezerQueryJson { 5 | private SearchResult[] data; 6 | private int total; 7 | 8 | public int getTotal() { 9 | return total; 10 | } 11 | 12 | public SearchResult[] getSearchResults() { 13 | return data; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/api/json/LittleTrack.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.api.json; 2 | 3 | /* 4 | That's a class that represents track object used in playlist json model 5 | (different to main track object) 6 | */ 7 | public class LittleTrack { 8 | 9 | private int id; 10 | private boolean readable; 11 | private String title; 12 | private String title_short; 13 | private String title_version; 14 | private boolean unseen; 15 | private String link; 16 | private int duration; 17 | private int rank; 18 | private boolean explicit_lyrics; 19 | private String preview; 20 | private long time_add; 21 | private Artist artist; 22 | private Album album; 23 | 24 | public int getId() { 25 | return id; 26 | } 27 | 28 | public boolean isReadable() { 29 | return readable; 30 | } 31 | 32 | public String getTitle() { 33 | return title; 34 | } 35 | 36 | public String getTitle_short() { 37 | return title_short; 38 | } 39 | 40 | public String getTitle_version() { 41 | return title_version; 42 | } 43 | 44 | public boolean isUnseen() { 45 | return unseen; 46 | } 47 | 48 | public String getLink() { 49 | return link; 50 | } 51 | 52 | public int getDuration() { 53 | return duration; 54 | } 55 | 56 | public int getRank() { 57 | return rank; 58 | } 59 | 60 | public boolean isExplicit_lyrics() { 61 | return explicit_lyrics; 62 | } 63 | 64 | public String getPreview() { 65 | return preview; 66 | } 67 | 68 | public long getTime_add() { 69 | return time_add; 70 | } 71 | 72 | public Artist getArtist() { 73 | return artist; 74 | } 75 | 76 | public Album getAlbum() { 77 | return album; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/api/json/LittleTrackContainer.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.api.json; 2 | 3 | public class LittleTrackContainer { 4 | private LittleTrack[] data; 5 | 6 | public LittleTrack[] getData() { 7 | return data; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/api/json/SearchResult.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.api.json; 2 | 3 | 4 | @SuppressWarnings("unused") 5 | public class SearchResult { 6 | private int id; 7 | private boolean readable; 8 | private String title; 9 | private String title_short; 10 | private String title_version; 11 | private String link; 12 | private int duration; 13 | private int rank; 14 | private boolean explicit_lyrics; 15 | private int explicit_content_lyrics; 16 | private int explicit_content_cover; 17 | private String preview; 18 | private Artist artist; 19 | private Album album; 20 | private String type; 21 | 22 | public int getId() { 23 | return id; 24 | } 25 | 26 | public boolean isReadable() { 27 | return readable; 28 | } 29 | 30 | public String getTitle() { 31 | return title; 32 | } 33 | 34 | public String getTitle_short() { 35 | return title_short; 36 | } 37 | 38 | public String getTitle_version() { 39 | return title_version; 40 | } 41 | 42 | public String getLink() { 43 | return link; 44 | } 45 | 46 | public int getDuration() { 47 | return duration; 48 | } 49 | 50 | public int getRank() { 51 | return rank; 52 | } 53 | 54 | public boolean isExplicit_lyrics() { 55 | return explicit_lyrics; 56 | } 57 | 58 | public int getExplicit_content_lyrics() { 59 | return explicit_content_lyrics; 60 | } 61 | 62 | public int getExplicit_content_cover() { 63 | return explicit_content_cover; 64 | } 65 | 66 | public String getPreview() { 67 | return preview; 68 | } 69 | 70 | public Artist getArtist() { 71 | return artist; 72 | } 73 | 74 | public Album getAlbum() { 75 | return album; 76 | } 77 | 78 | public String getType() { 79 | return type; 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/api/json/Track.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.api.json; 2 | 3 | import java.time.LocalDate; 4 | 5 | public class Track { 6 | private long id; 7 | private boolean readable; 8 | private String title; 9 | private String title_short; 10 | private String title_version; 11 | private boolean unseen; 12 | private String isrc; 13 | private String link; 14 | private String share; 15 | private int duration; 16 | private int track_position; 17 | private int disk_number; 18 | private int rank; 19 | private LocalDate release_date; 20 | private boolean explicit_lyrics; 21 | private int explicit_content_lyrics; 22 | private int explicit_content_cover; 23 | private String preview; 24 | private double bpm; 25 | private double gain; 26 | private String[] available_countries; 27 | private Track alternative; 28 | private String[] contributors; 29 | 30 | private Artist artist; 31 | private Album album; 32 | 33 | public long getId() { 34 | return id; 35 | } 36 | 37 | public boolean isReadable() { 38 | return readable; 39 | } 40 | 41 | public String getTitle() { 42 | return title; 43 | } 44 | 45 | public String getTitle_short() { 46 | return title_short; 47 | } 48 | 49 | public String getTitle_version() { 50 | return title_version; 51 | } 52 | 53 | public boolean isUnseen() { 54 | return unseen; 55 | } 56 | 57 | public String getIsrc() { 58 | return isrc; 59 | } 60 | 61 | public String getLink() { 62 | return link; 63 | } 64 | 65 | public String getShare() { 66 | return share; 67 | } 68 | 69 | public int getDuration() { 70 | return duration; 71 | } 72 | 73 | public int getTrack_position() { 74 | return track_position; 75 | } 76 | 77 | public int getDisk_number() { 78 | return disk_number; 79 | } 80 | 81 | public int getRank() { 82 | return rank; 83 | } 84 | 85 | public LocalDate getRelease_date() { 86 | return release_date; 87 | } 88 | 89 | public boolean isExplicit_lyrics() { 90 | return explicit_lyrics; 91 | } 92 | 93 | public int getExplicit_content_lyrics() { 94 | return explicit_content_lyrics; 95 | } 96 | 97 | public int getExplicit_content_cover() { 98 | return explicit_content_cover; 99 | } 100 | 101 | public String getPreview() { 102 | return preview; 103 | } 104 | 105 | public double getBpm() { 106 | return bpm; 107 | } 108 | 109 | public double getGain() { 110 | return gain; 111 | } 112 | 113 | public String[] getAvailable_countries() { 114 | return available_countries; 115 | } 116 | 117 | public Track getAlternative() { 118 | return alternative; 119 | } 120 | 121 | public String[] getContributors() { 122 | return contributors; 123 | } 124 | 125 | public Artist getArtist() { 126 | return artist; 127 | } 128 | 129 | public Album getAlbum() { 130 | return album; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/api/json/User.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.api.json; 2 | 3 | import java.time.LocalDate; 4 | 5 | public class User { 6 | private long id; 7 | private String name; 8 | private String lastname; 9 | private String firstname; 10 | private String email; 11 | private int status; 12 | private LocalDate birthday; 13 | private LocalDate inscription_date; 14 | private String gender; 15 | private String link; 16 | private String picture; 17 | private String picture_small; 18 | private String picture_medium; 19 | private String picture_big; 20 | private String picture_xl; 21 | private String country; 22 | private String lang; 23 | private boolean is_kid; 24 | private String explicit_content_level; 25 | private String[] explicit_content_levels_available; 26 | private String tracklist; 27 | 28 | public long getId() { 29 | return id; 30 | } 31 | 32 | public String getName() { 33 | return name; 34 | } 35 | 36 | public String getLastname() { 37 | return lastname; 38 | } 39 | 40 | public String getFirstname() { 41 | return firstname; 42 | } 43 | 44 | public String getEmail() { 45 | return email; 46 | } 47 | 48 | public int getStatus() { 49 | return status; 50 | } 51 | 52 | public LocalDate getBirthday() { 53 | return birthday; 54 | } 55 | 56 | public LocalDate getInscription_date() { 57 | return inscription_date; 58 | } 59 | 60 | public String getGender() { 61 | return gender; 62 | } 63 | 64 | public String getLink() { 65 | return link; 66 | } 67 | 68 | public String getPicture() { 69 | return picture; 70 | } 71 | 72 | public String getPicture_small() { 73 | return picture_small; 74 | } 75 | 76 | public String getPicture_medium() { 77 | return picture_medium; 78 | } 79 | 80 | public String getPicture_big() { 81 | return picture_big; 82 | } 83 | 84 | public String getPicture_xl() { 85 | return picture_xl; 86 | } 87 | 88 | public String getCountry() { 89 | return country; 90 | } 91 | 92 | public String getLang() { 93 | return lang; 94 | } 95 | 96 | public boolean isIs_kid() { 97 | return is_kid; 98 | } 99 | 100 | public String getExplicit_content_level() { 101 | return explicit_content_level; 102 | } 103 | 104 | public String[] getExplicit_content_levels_available() { 105 | return explicit_content_levels_available; 106 | } 107 | 108 | public String getTracklist() { 109 | return tracklist; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/bot/Bot.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.bot; 2 | 3 | import com.vdurmont.emoji.EmojiParser; 4 | import com.wrapper.spotify.exceptions.SpotifyWebApiException; 5 | import me.reply.deemixbot.playlistchecker.DeezerPlaylistChecker; 6 | import me.reply.deemixbot.api.json.SearchResult; 7 | import me.reply.deemixbot.playlistchecker.SpotifyPlaylistChecker; 8 | import me.reply.deemixbot.users.DownloadMode; 9 | import me.reply.deemixbot.users.User; 10 | import me.reply.deemixbot.users.UserManager; 11 | import me.reply.deemixbot.utils.ReplyKeyboardBuilder; 12 | import org.apache.hc.core5.http.ParseException; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.telegram.telegrambots.bots.TelegramLongPollingBot; 16 | import org.telegram.telegrambots.meta.api.methods.AnswerInlineQuery; 17 | import org.telegram.telegrambots.meta.api.methods.send.SendAudio; 18 | import org.telegram.telegrambots.meta.api.methods.send.SendDocument; 19 | import org.telegram.telegrambots.meta.api.methods.send.SendMessage; 20 | import org.telegram.telegrambots.meta.api.objects.Update; 21 | import org.telegram.telegrambots.meta.api.objects.inlinequery.InlineQuery; 22 | import org.telegram.telegrambots.meta.api.objects.inlinequery.inputmessagecontent.InputTextMessageContent; 23 | import org.telegram.telegrambots.meta.api.objects.inlinequery.result.InlineQueryResult; 24 | import org.telegram.telegrambots.meta.api.objects.inlinequery.result.InlineQueryResultArticle; 25 | import org.telegram.telegrambots.meta.api.objects.replykeyboard.ReplyKeyboardMarkup; 26 | import org.telegram.telegrambots.meta.exceptions.TelegramApiException; 27 | 28 | import java.io.File; 29 | import java.io.IOException; 30 | import java.net.MalformedURLException; 31 | import java.util.ArrayList; 32 | import java.util.Base64; 33 | import java.util.List; 34 | import java.util.concurrent.ExecutionException; 35 | import java.util.concurrent.ExecutorService; 36 | import java.util.concurrent.Executors; 37 | import java.util.concurrent.Future; 38 | 39 | public class Bot extends TelegramLongPollingBot { 40 | private final Config c; 41 | 42 | private static Bot instance; 43 | private final UserManager userManager; 44 | private final CommandHandler commandHandler; 45 | private static final Logger logger = LoggerFactory.getLogger(Bot.class); 46 | private static final int CACHETIME = 3600; 47 | 48 | private final ExecutorService executorService; 49 | 50 | public static Bot getInstance(){ 51 | return instance; 52 | } 53 | 54 | public Bot(Config c,UserManager userManager){ 55 | instance = this; 56 | this.userManager = userManager; 57 | commandHandler = new CommandHandler(); 58 | this.c = c; 59 | executorService = Executors.newFixedThreadPool(100); 60 | } 61 | 62 | public UserManager getUserManager() { 63 | return userManager; 64 | } 65 | 66 | public void onUpdateReceived(Update update) { 67 | if (update.hasInlineQuery()) { 68 | handleIncomingInlineQuery(update.getInlineQuery()); 69 | } 70 | else if(update.hasMessage()){ 71 | String text = update.getMessage().getText(); 72 | if(text == null) 73 | return; 74 | long chat_id = update.getMessage().getChatId(); 75 | String user_id = update.getMessage().getFrom().getId().toString(); 76 | 77 | User currentUser; 78 | if(!userManager.isInList(user_id)){ 79 | currentUser = new User(user_id,c.getAnti_flood_cooldown()); 80 | userManager.addUser(currentUser); 81 | logger.info("Added new user: " + user_id); 82 | } 83 | else currentUser = userManager.getUser(user_id); 84 | 85 | if(commandHandler.handle(text,chat_id,user_id)) //if the user has typed a known command 86 | return; 87 | 88 | if(text.startsWith("https://t.me")){ // text has used inline bot in private chat 89 | text = text.substring(text.indexOf('=') + 1); 90 | text = new String(Base64.getDecoder().decode(text)); 91 | } 92 | else if(text.startsWith("/start")){ 93 | text = text.substring(7); // remove "/start" to the query 94 | text = new String(Base64.getDecoder().decode(text)); 95 | } 96 | else if(text.startsWith("/")){ 97 | sendMessage(":x: Unknown command.",chat_id); 98 | return; 99 | } 100 | 101 | if(!currentUser.isCanMakeRequest()){ 102 | sendMessage(":x: You have to wait for DeemixBot to finish with your current request before sending another one.",chat_id); 103 | return; 104 | } 105 | 106 | if(!currentUser.isCanType()){ 107 | sendMessage(":x: You have to wait " + c.getAnti_flood_cooldown() + " seconds before make a new request.",chat_id); 108 | return; 109 | } 110 | 111 | currentUser.startAntiFlood(); 112 | if(isLink(text)) { 113 | if(isSpotifyLink(text)){ 114 | SpotifyPlaylistChecker spotifyPlaylistChecker = null; 115 | try { 116 | spotifyPlaylistChecker = new SpotifyPlaylistChecker(text,c); 117 | } catch (ParseException | SpotifyWebApiException | IOException e) { 118 | logger.error(e.getMessage()); 119 | if(c.isDebug_mode()) 120 | e.printStackTrace(); 121 | } 122 | 123 | if(!(spotifyPlaylistChecker != null && spotifyPlaylistChecker.isReasonable())){ 124 | sendMessage(":x: Playlist contains too many tracks, only 100 or less are supported.",chat_id); 125 | return; 126 | } 127 | } 128 | else if(isDeezerLink(text)){ 129 | if(isDeezerPlaylist(text)){ 130 | DeezerPlaylistChecker deezerPlaylistChecker = new DeezerPlaylistChecker(text,c); 131 | try { 132 | if(!deezerPlaylistChecker.isReasonable()){ 133 | sendMessage(":x: Playlist contains too many tracks, only 100 or less are supported.",chat_id); 134 | return; 135 | } 136 | } catch (MalformedURLException e) { 137 | logger.error(e.getMessage()); 138 | if(c.isDebug_mode()) 139 | e.printStackTrace(); 140 | return; 141 | } 142 | } 143 | executorService.submit(new DownloadJob(text, chat_id, c,currentUser)); 144 | sendMessage(":arrow_down: I'm downloading your music, please wait...",chat_id); 145 | return; 146 | } 147 | sendMessage(":x: This link is not compatible.",chat_id); 148 | } 149 | else{ 150 | try { 151 | Future resultFuture = executorService.submit(new JsonFetcher(text)); 152 | SearchResult[] results = resultFuture.get(); 153 | if(results == null) { 154 | sendMessage(":x: No results...", chat_id); 155 | return; 156 | } 157 | else 158 | sendMessage(":arrow_down: I'm downloading your music, please wait...",chat_id); 159 | 160 | SearchResult firstElement = results[0]; 161 | DownloadMode userDownloadMode = userManager.getMode(user_id); 162 | switch (userDownloadMode){ 163 | case ALBUM: 164 | executorService.submit(new DownloadJob(firstElement.getAlbum().getLink(),chat_id,c,currentUser)); 165 | break; 166 | case TRACK: 167 | executorService.submit(new DownloadJob(firstElement.getLink(),chat_id,c,currentUser)); 168 | break; 169 | } 170 | } catch (InterruptedException | ExecutionException e) { 171 | logger.error(e.getMessage()); 172 | e.printStackTrace(); 173 | } 174 | } 175 | } 176 | } 177 | 178 | public String getBotUsername() { 179 | return c.getBot_username(); 180 | } 181 | 182 | public String getBotToken() { 183 | return c.getBot_token(); 184 | } 185 | 186 | public void sendMessage(String text,long chatId){ 187 | SendMessage message = new SendMessage() 188 | .setText(EmojiParser.parseToUnicode(text)) 189 | .setChatId(chatId); 190 | try { 191 | execute(message); 192 | } catch (TelegramApiException e) { 193 | e.printStackTrace(); 194 | logger.error(e.getMessage()); 195 | } 196 | } 197 | 198 | @Deprecated 199 | public void sendDocument(File f,long chat_id,String text){ 200 | SendDocument document = new SendDocument() 201 | .setDocument(f) 202 | .setChatId(chat_id) 203 | .setCaption(EmojiParser.parseToUnicode(text)); 204 | try { 205 | execute(document); 206 | } catch (TelegramApiException e) { 207 | logger.error(e.getMessage()); 208 | e.printStackTrace(); 209 | } 210 | } 211 | 212 | public void sendAudio(File f,long chat_id,String text){ 213 | SendAudio audio = new SendAudio() 214 | .setAudio(f) 215 | .setChatId(chat_id) 216 | .setCaption(EmojiParser.parseToUnicode(text)); 217 | try { 218 | execute(audio); 219 | } catch (TelegramApiException e) { 220 | logger.error(e.getMessage()); 221 | e.printStackTrace(); 222 | } 223 | } 224 | 225 | private boolean isLink(String link){ 226 | return link.matches("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"); 227 | } 228 | 229 | private boolean isDeezerLink(String link){ 230 | return link.startsWith("https://www.deezer.com/"); 231 | } 232 | 233 | private boolean isDeezerPlaylist(String link){ 234 | if(!isDeezerLink(link)) 235 | return false; 236 | return link.contains("/playlist/"); 237 | } 238 | 239 | private boolean isSpotifyLink(String link){ 240 | return link.contains("open.spotify.com"); 241 | // return link.startsWith("https://open.spotify.com/"); 242 | } 243 | 244 | public void sendKeyboard(String text,long chatId){ 245 | ReplyKeyboardMarkup replyKeyboardMarkup = ReplyKeyboardBuilder.createReply() 246 | .row() 247 | .addText(EmojiParser.parseToUnicode(":cd: Track mode")) 248 | .addText(EmojiParser.parseToUnicode(":notebook_with_decorative_cover: Album mode")) 249 | .row() 250 | .addText(EmojiParser.parseToUnicode(":large_blue_diamond: Flac")) 251 | .addText(EmojiParser.parseToUnicode(":large_orange_diamond: MP3")) 252 | .row() 253 | .addText(EmojiParser.parseToUnicode(":computer: Source code")) 254 | .build(); 255 | 256 | SendMessage message = new SendMessage() 257 | .setText(EmojiParser.parseToUnicode(text)) 258 | .setReplyMarkup(replyKeyboardMarkup) 259 | .setChatId(chatId); 260 | try { 261 | execute(message); 262 | } catch (TelegramApiException e) { 263 | logger.error(e.getMessage()); 264 | e.printStackTrace(); 265 | } 266 | } 267 | 268 | private void handleIncomingInlineQuery(InlineQuery inlineQuery) { 269 | String query = EmojiParser.removeAllEmojis(inlineQuery.getQuery()); 270 | logger.debug("Searching: " + query); 271 | try { 272 | if (!query.isEmpty()) { 273 | Future resultFuture = executorService.submit(new JsonFetcher(query)); 274 | SearchResult[] results = resultFuture.get(); 275 | execute(convertResultsToResponse(inlineQuery, results)); 276 | } else { 277 | execute(convertResultsToResponse(inlineQuery, new SearchResult[]{})); // send empty answer 278 | } 279 | } catch (TelegramApiException | InterruptedException | ExecutionException e) { 280 | logger.error(e.getMessage()); 281 | if(c.isDebug_mode()) 282 | e.printStackTrace(); 283 | } 284 | } 285 | 286 | private AnswerInlineQuery convertResultsToResponse(InlineQuery inlineQuery, SearchResult[] results) { 287 | AnswerInlineQuery answerInlineQuery = new AnswerInlineQuery(); 288 | answerInlineQuery.setInlineQueryId(inlineQuery.getId()); 289 | answerInlineQuery.setCacheTime(CACHETIME); 290 | answerInlineQuery.setResults(convertDeezerResultsToInline(results)); 291 | return answerInlineQuery; 292 | } 293 | 294 | private List convertDeezerResultsToInline(SearchResult[] deezerResults){ 295 | List results = new ArrayList<>(); 296 | int i = 0; 297 | for(SearchResult d : deezerResults){ 298 | String url = "https://t.me/" + c.getBot_username() + "?start=" + 299 | Base64.getEncoder().encodeToString(d.getLink().getBytes()); 300 | InlineQueryResultArticle article = new InlineQueryResultArticle(); 301 | InputTextMessageContent messageContent = new InputTextMessageContent(); 302 | //messageContent.disableWebPagePreview(); 303 | messageContent.enableMarkdown(true); 304 | messageContent.setMessageText(d.getLink()); 305 | article.setInputMessageContent(messageContent); 306 | article.setId(Integer.toString(i)); 307 | article.setTitle(d.getTitle()); 308 | article.setDescription(d.getArtist().getName()); 309 | article.setThumbUrl(d.getAlbum().getCover_medium()); 310 | article.setReplyMarkup( 311 | ReplyKeyboardBuilder 312 | .createInline() 313 | .row() 314 | .addUrl(EmojiParser.parseToUnicode(":arrow_down: Download"),url) 315 | .row() 316 | .build() 317 | ); 318 | results.add(article); 319 | i++; 320 | } 321 | return results; 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/bot/CommandHandler.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.bot; 2 | 3 | import com.vdurmont.emoji.EmojiParser; 4 | import me.reply.deemixbot.users.DownloadFormat; 5 | import me.reply.deemixbot.users.DownloadMode; 6 | 7 | public class CommandHandler { 8 | 9 | // This method returns true if the user has typed a known command 10 | public boolean handle(String text,long chat_id,String userId){ 11 | if(text == null || userId == null) 12 | return false; 13 | switch(EmojiParser.parseToAliases(text)){ 14 | case "/start": 15 | Bot.getInstance().sendKeyboard("Hi, welcome to Deemixbot :musical_note:, i'm here to spread the music all over the world :earth_africa:",chat_id); 16 | Bot.getInstance().sendMessage("To start, just type a song or album name",chat_id); 17 | return true; 18 | case "/bug": 19 | Bot.getInstance().sendKeyboard("DeemixBot is still in beta phase, so it's normal that there are some inconveniences, such as unsent music or other problems. \n\nIf you want to report a bug do so via Github issues at https://github.com/replydev/DeemixBot/issues, taking care to specify the type of bug and how to replicate it. \n\nI will do my best to fix it as soon as possible. Thanks for your help!",chat_id); 20 | return true; 21 | case ":cd: Track mode": 22 | Bot.getInstance().getUserManager().setMode(userId, DownloadMode.TRACK); 23 | Bot.getInstance().sendMessage("Track mode enabled! :cd:",chat_id); 24 | return true; 25 | case ":notebook_with_decorative_cover: Album mode": 26 | Bot.getInstance().getUserManager().setMode(userId, DownloadMode.ALBUM); 27 | Bot.getInstance().sendMessage("Album mode enabled! :notebook_with_decorative_cover:",chat_id); 28 | return true; 29 | case ":large_blue_diamond: Flac": 30 | Bot.getInstance().getUserManager().setFormat(userId, DownloadFormat.FLAC); 31 | Bot.getInstance().sendMessage("Flac mode enabled! :large_blue_diamond:",chat_id); 32 | return true; 33 | case ":large_orange_diamond: MP3": 34 | Bot.getInstance().getUserManager().setFormat(userId, DownloadFormat.MP3); 35 | Bot.getInstance().sendMessage("MP3 mode enabled! :large_orange_diamond:",chat_id); 36 | return true; 37 | case ":computer: Source code": 38 | Bot.getInstance().sendMessage(":smile_cat: Developed by @zreply. Thanks a lot to Deemix developer for make this possible. \n:page_facing_up: The source code of this bot is open source, feel free to check, any pull request is welcome!\n:link: https://github.com/replydev/DeemixBot\n:link:https://notabug.org/RemixDev/deemix",chat_id); 39 | return true; 40 | default: return false; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/bot/Config.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.bot; 2 | 3 | import com.google.gson.Gson; 4 | import org.apache.commons.io.FileUtils; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | 9 | public class Config { 10 | 11 | private final String spotify_client_id; 12 | private final String spotify_client_secret; 13 | private final int max_playlist_tracks; 14 | private final String bot_token; 15 | private final String bot_username; 16 | private final int anti_flood_cooldown; 17 | private final int kill_python_process_cooldown; 18 | private final String python_executable; 19 | private final int save_users_list_cooldown; 20 | private final boolean debug_mode; 21 | private static final String CONFIG_FILENAME = "deemix_bot_config.json"; 22 | 23 | public String getPython_executable() { 24 | return python_executable; 25 | } 26 | 27 | private Config(String spotify_client_id, 28 | String spotify_client_secret, 29 | int max_playlist_tracks, 30 | String bot_token, 31 | String bot_username, 32 | int kill_python_process_cooldown, 33 | String python_executable, 34 | int save_users_list_cooldown, 35 | int anti_flood_cooldown, 36 | boolean debug_mode 37 | ){ 38 | this.spotify_client_id = spotify_client_id; 39 | this.spotify_client_secret = spotify_client_secret; 40 | this.max_playlist_tracks = max_playlist_tracks; 41 | this.bot_token = bot_token; 42 | this.bot_username = bot_username; 43 | this.kill_python_process_cooldown = kill_python_process_cooldown; 44 | this.python_executable = python_executable; 45 | this.save_users_list_cooldown = save_users_list_cooldown; 46 | this.anti_flood_cooldown = anti_flood_cooldown; 47 | this.debug_mode = debug_mode; 48 | } 49 | 50 | public static Config loadFromFile() throws IOException { 51 | Gson g = new Gson(); 52 | File f = new File(CONFIG_FILENAME); 53 | if(!f.exists()) 54 | saveDefaultConfig(); 55 | 56 | return g.fromJson(FileUtils.readFileToString(f,"UTF-8"),Config.class); 57 | } 58 | 59 | private static void saveDefaultConfig() throws IOException { 60 | String defaultConfig = "{\n" + 61 | " \"spotify_client_id\": \"spotify_client_id_here\",\n" + 62 | " \"spotify_client_secret\": \"spotify_client_secret_here\",\n" + 63 | " \"max_playlist_tracks\": 100,\n" + 64 | " \"bot_token\": \"bot_token_here\",\n" + 65 | " \"bot_username\": \"bot_username_here\",\n" + 66 | " \"kill_python_process_cooldown\": 5000,\n" + 67 | " \"python_executable\": 5000,\n" + 68 | " \"save_users_list_cooldown\": 86400000,\n" + 69 | " \"anti_flood_cooldown\": \"python3\",\n" + 70 | " \"debug_mode\": false\n" + 71 | "}"; 72 | FileUtils.write(new File(CONFIG_FILENAME),defaultConfig,"UTF-8"); 73 | } 74 | 75 | public String getSpotifyClientId() { 76 | return spotify_client_id; 77 | } 78 | 79 | public String getSpotify_client_secret(){ 80 | return spotify_client_secret; 81 | } 82 | 83 | public int getMax_playlist_tracks() { 84 | return max_playlist_tracks; 85 | } 86 | 87 | public String getBot_token() { 88 | return bot_token; 89 | } 90 | 91 | public String getBot_username() { 92 | return bot_username; 93 | } 94 | 95 | public boolean isDebug_mode() { 96 | return debug_mode; 97 | } 98 | 99 | public int getAnti_flood_cooldown() { 100 | return anti_flood_cooldown; 101 | } 102 | 103 | public int getKill_python_process_cooldown() { 104 | return kill_python_process_cooldown; 105 | } 106 | 107 | public int getSave_users_list_cooldown() { 108 | return save_users_list_cooldown; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/bot/DownloadJob.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.bot; 2 | 3 | import me.reply.deemixbot.users.DownloadFormat; 4 | import me.reply.deemixbot.users.User; 5 | import org.apache.commons.io.FileUtils; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.io.InputStreamReader; 13 | import java.util.Vector; 14 | 15 | public class DownloadJob implements Runnable{ 16 | 17 | private final String link; 18 | private final long chat_id; 19 | private static final Logger logger = LoggerFactory.getLogger(DownloadJob.class); 20 | private final Config c; 21 | private final User user; 22 | private int downloadedSongs; 23 | private Process process; 24 | 25 | private String folderName; 26 | private String errors; 27 | 28 | public DownloadJob(String link,long chat_id,Config c,User u){ 29 | this.link = link; 30 | this.chat_id = chat_id; 31 | this.c = c; 32 | errors = null; 33 | this.user = u; 34 | downloadedSongs = 0; 35 | } 36 | 37 | @Override 38 | public void run() { 39 | try { 40 | user.setCanMakeRequest(false); 41 | String dirName = job(); 42 | FileUtils.deleteDirectory(new File(dirName)); 43 | user.setCanMakeRequest(true); 44 | } catch (IOException | InterruptedException e) { 45 | logger.error("Error during download job execution: " + e.getMessage()); 46 | e.printStackTrace(); 47 | }finally { 48 | Bot.getInstance().sendMessage(":white_check_mark: I'm done.\n\n:information_source: Type /bug if you faced any bug.",chat_id); 49 | } 50 | } 51 | 52 | private String job() throws IOException, InterruptedException { 53 | // Create the process 54 | String[] commands = user.getDownloadFormat().equals(DownloadFormat.FLAC) ? 55 | new String[]{c.getPython_executable(), "-m", "deemix", "-b", "flac", "-l", link} : new String[]{c.getPython_executable(), "-m", "deemix", "-l", link}; 56 | ProcessBuilder builder = new ProcessBuilder(commands); 57 | builder.redirectErrorStream(true); 58 | process = builder.start(); 59 | 60 | // Fetch deemix output using the process object 61 | boolean hasOutput = false; 62 | BufferedReader stdInput = new BufferedReader(new 63 | InputStreamReader(process.getInputStream())); 64 | String s; 65 | while ((s = stdInput.readLine()) != null){ 66 | if(c.isDebug_mode()) 67 | System.out.println(s); 68 | handleLog(s); 69 | hasOutput = true; 70 | } 71 | 72 | if(!hasOutput){ 73 | logger.error("Error during download job execution: no deemix output."); 74 | } 75 | process.waitFor();// now the program can stop the process if the user has requested a playlist bigger than x tracks 76 | sendAllFiles(folderName); //check if we have missed something 77 | if(errors != null) //check if we got some errors 78 | Bot.getInstance().sendMessage(errors,chat_id); 79 | return folderName; 80 | } 81 | 82 | private void sendAllFiles(String dirname) throws IOException, InterruptedException { 83 | File dir = new File(dirname); 84 | if(!dir.isDirectory()){ 85 | logger.error("Error during music sending: \"" + dirname + "\" is not a directory."); 86 | return; 87 | } 88 | 89 | File[] files = dir.listFiles(); 90 | if(files == null){ 91 | logger.error("Error during music sending: directory is empty."); 92 | return; 93 | } 94 | for(File temp : files){ 95 | if(temp.isFile()){ 96 | sendFile(temp); 97 | } 98 | else 99 | sendAllFiles(temp.getPath()); 100 | } 101 | } 102 | 103 | private void sendFile(File d) throws IOException, InterruptedException { 104 | if(!d.isDirectory()){ 105 | // let's skip unwanted files 106 | if(d.getName().equalsIgnoreCase("cover.jpg")) 107 | return; 108 | // and save the errors 109 | else if(d.getName().equalsIgnoreCase("errors.txt")){ 110 | errors = ":x: Some errors has occurred:\n" + FileUtils.readFileToString(d,"UTF-8"); 111 | return; 112 | } 113 | downloadedSongs++; 114 | Bot.getInstance().sendAudio(d,chat_id,":star: @" + c.getBot_username()); 115 | FileUtils.forceDelete(d); 116 | if(downloadedSongs >= c.getMax_playlist_tracks()){ //if the user requested too many songs 117 | new Thread(() -> process.destroy()).start(); // try to kill the process lightly 118 | Thread.sleep(c.getKill_python_process_cooldown()); // wait some time 119 | if(process.isAlive()) 120 | process.destroyForcibly(); // if it's still alive destroy the process 121 | } 122 | } 123 | else 124 | sendAllFiles(d.getPath()); 125 | } 126 | 127 | private void handleLog(String line){ 128 | if(line.contains("Track download completed")){ // a song has been downloaded by deemix 129 | String trackName = line.substring(line.indexOf('[') + 1, line.lastIndexOf(']')); 130 | File trackFile = findFile(trackName); //Remember this when implementing FLAC files 131 | if(trackFile == null) //if we didn't find the file 132 | return; 133 | logger.info("New file downloaded: " + trackFile.getPath()); 134 | try { 135 | sendFile(trackFile); 136 | } catch (IOException | InterruptedException e) { 137 | logger.error(e.getMessage()); 138 | e.printStackTrace(); 139 | } 140 | } 141 | //INFO:deemix:Using a local download folder: tchgmfqtklza 142 | else if(line.contains("Using a local download folder")){ //we can fetch the folder name 143 | folderName = line.substring(line.lastIndexOf(':') + 2); 144 | logger.info("Fetched a new folder: " + folderName); 145 | } 146 | } 147 | 148 | private File findFile(String filename){ 149 | String trackName = filename.substring(filename.indexOf('-') + 2); 150 | File dir = new File(folderName); 151 | Vector files = getAllFiles(dir); 152 | if(files == null) 153 | return null; 154 | for(File f : files){ 155 | if(f.getName().contains(trackName)){ 156 | return f; 157 | } 158 | } 159 | return null; 160 | } 161 | 162 | private Vector getAllFiles(File dir){ 163 | File[] files = dir.listFiles(); 164 | 165 | if(files == null) 166 | return null; 167 | Vector trueFiles = new Vector<>(); 168 | 169 | for(File temp : files){ 170 | if(temp.isFile()) 171 | trueFiles.add(temp); 172 | else 173 | trueFiles.addAll(getAllFiles(temp)); 174 | } 175 | return trueFiles; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/bot/JsonFetcher.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.bot; 2 | 3 | import me.reply.deemixbot.api.Deezer; 4 | import me.reply.deemixbot.api.json.DeezerQueryJson; 5 | import me.reply.deemixbot.api.json.SearchResult; 6 | 7 | import java.util.concurrent.Callable; 8 | 9 | public class JsonFetcher implements Callable { 10 | 11 | private final String text; 12 | 13 | public JsonFetcher(String text){ 14 | this.text = text; 15 | } 16 | 17 | @Override 18 | public SearchResult[] call() throws Exception { 19 | DeezerQueryJson deezerQueryJson = Deezer.getQuery(text); 20 | if(deezerQueryJson == null) 21 | return null; 22 | if(deezerQueryJson.getTotal() <= 0) 23 | return null; 24 | else return deezerQueryJson.getSearchResults(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/playlistchecker/DeezerPlaylistChecker.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.playlistchecker; 2 | 3 | import me.reply.deemixbot.api.Deezer; 4 | import me.reply.deemixbot.playlistchecker.PlaylistChecker; 5 | import me.reply.deemixbot.api.json.DeezerPlaylistSearchResult; 6 | import me.reply.deemixbot.bot.Config; 7 | 8 | import java.net.MalformedURLException; 9 | 10 | public class DeezerPlaylistChecker extends PlaylistChecker { 11 | 12 | private String playlistId; 13 | 14 | public DeezerPlaylistChecker(String link,Config c){ 15 | initialize(link,c); 16 | } 17 | 18 | @Override 19 | protected void initialize(String link, Config c) { 20 | this.c = c; 21 | this.playlistId = link.substring(link.lastIndexOf('/') + 1); 22 | } 23 | 24 | @Override 25 | public boolean isReasonable() throws MalformedURLException { 26 | DeezerPlaylistSearchResult deezerPlaylistSearchResult = Deezer.getPlaylist(playlistId); 27 | if(deezerPlaylistSearchResult == null) 28 | return false; 29 | return deezerPlaylistSearchResult.getNb_tracks() <= c.getMax_playlist_tracks(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/playlistchecker/PlaylistChecker.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.playlistchecker; 2 | 3 | import com.wrapper.spotify.exceptions.SpotifyWebApiException; 4 | import me.reply.deemixbot.bot.Config; 5 | import org.apache.hc.core5.http.ParseException; 6 | 7 | import java.io.IOException; 8 | import java.net.MalformedURLException; 9 | 10 | public abstract class PlaylistChecker { 11 | protected Config c; 12 | protected abstract void initialize(String link,Config c) throws ParseException, SpotifyWebApiException, IOException; 13 | public abstract boolean isReasonable() throws MalformedURLException; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/playlistchecker/SpotifyPlaylistChecker.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.playlistchecker; 2 | 3 | import com.wrapper.spotify.SpotifyApi; 4 | import com.wrapper.spotify.exceptions.SpotifyWebApiException; 5 | import com.wrapper.spotify.model_objects.credentials.ClientCredentials; 6 | import com.wrapper.spotify.model_objects.specification.Playlist; 7 | import com.wrapper.spotify.requests.authorization.client_credentials.ClientCredentialsRequest; 8 | import com.wrapper.spotify.requests.data.playlists.GetPlaylistRequest; 9 | import me.reply.deemixbot.bot.Config; 10 | import org.apache.hc.core5.http.ParseException; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.io.IOException; 15 | 16 | public class SpotifyPlaylistChecker extends PlaylistChecker { 17 | 18 | private static final Logger logger = LoggerFactory.getLogger(SpotifyPlaylistChecker.class); 19 | private GetPlaylistRequest getPlaylistRequest; 20 | 21 | public SpotifyPlaylistChecker(String link, Config c) throws ParseException, SpotifyWebApiException, IOException{ 22 | initialize(link,c); 23 | } 24 | 25 | @Override 26 | protected void initialize(String link, Config c) throws SpotifyWebApiException, IOException, ParseException { 27 | this.c = c; 28 | String playlistId = link.substring(link.lastIndexOf('/') + 1); 29 | 30 | SpotifyApi spotifyApi = new SpotifyApi.Builder() 31 | .setClientId(c.getSpotifyClientId()) 32 | .setClientSecret(c.getSpotify_client_secret()) 33 | .build(); 34 | 35 | ClientCredentialsRequest clientCredentialsRequest = spotifyApi.clientCredentials() 36 | .build(); 37 | 38 | final ClientCredentials clientCredentials = clientCredentialsRequest.execute(); 39 | 40 | spotifyApi.setAccessToken(clientCredentials.getAccessToken()); 41 | 42 | getPlaylistRequest = spotifyApi.getPlaylist(playlistId).build(); 43 | } 44 | 45 | @Override 46 | public boolean isReasonable(){ 47 | try { 48 | final Playlist playlist = getPlaylistRequest.execute(); 49 | return playlist.getTracks().getTotal() <= c.getMax_playlist_tracks(); 50 | } catch (IOException | SpotifyWebApiException | ParseException e) { 51 | logger.error(e.getMessage()); 52 | return false; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/users/DownloadFormat.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.users; 2 | 3 | public enum DownloadFormat { 4 | FLAC, 5 | MP3 6 | } -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/users/DownloadMode.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.users; 2 | 3 | public enum DownloadMode { 4 | TRACK, 5 | ALBUM 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/users/User.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.users; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | import java.util.concurrent.Executors; 5 | 6 | public class User { 7 | private final String id; 8 | private DownloadMode downloadMode; 9 | private DownloadFormat downloadFormat; 10 | private boolean canType; 11 | private boolean canMakeRequest; 12 | private final int cooldown; 13 | 14 | public User(String id,int cooldown){ 15 | this.id = id; 16 | this.downloadMode = DownloadMode.TRACK; 17 | this.downloadFormat = DownloadFormat.MP3; 18 | this.canType = true; 19 | this.canMakeRequest = true; 20 | this.cooldown = cooldown; 21 | } 22 | 23 | public String getId() { 24 | return id; 25 | } 26 | 27 | public DownloadMode getDownloadMode() { 28 | return downloadMode; 29 | } 30 | 31 | public DownloadFormat getDownloadFormat() { 32 | return downloadFormat; 33 | } 34 | 35 | public void setDownloadMode(DownloadMode downloadMode) { 36 | this.downloadMode = downloadMode; 37 | } 38 | 39 | public boolean isCanType() { 40 | return canType; 41 | } 42 | 43 | public boolean isCanMakeRequest() { 44 | return canMakeRequest; 45 | } 46 | 47 | public void startAntiFlood(){ 48 | canType = false; 49 | ExecutorService cooldownService = Executors.newSingleThreadExecutor(); 50 | cooldownService.execute(() -> { 51 | try { 52 | Thread.sleep(cooldown*1000); 53 | } catch (InterruptedException e) { 54 | e.printStackTrace(); 55 | } 56 | canType = true; 57 | }); 58 | } 59 | 60 | public void setCanMakeRequest(boolean canMakeRequest) { 61 | this.canMakeRequest = canMakeRequest; 62 | } 63 | 64 | public void setDownloadFormat(DownloadFormat downloadFormat) { 65 | this.downloadFormat = downloadFormat; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/users/UserManager.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.users; 2 | 3 | import java.util.Vector; 4 | 5 | public class UserManager { 6 | 7 | public final Vector users; 8 | 9 | public UserManager(){ 10 | users = new Vector<>(); 11 | } 12 | 13 | public void addUser(User u){ 14 | users.add(u); 15 | } 16 | 17 | public DownloadMode getMode(String userId){ 18 | for(User u : users){ 19 | if(u.getId().equalsIgnoreCase(userId)) 20 | return u.getDownloadMode(); 21 | } 22 | return DownloadMode.TRACK; 23 | } 24 | 25 | public User getUser(String id){ 26 | for(User u : users){ 27 | if(u.getId().equalsIgnoreCase(id)) 28 | return u; 29 | } 30 | return null; 31 | } 32 | 33 | public void setMode(String userId,DownloadMode downloadMode){ 34 | for(User u : users){ 35 | if(u.getId().equalsIgnoreCase(userId)){ 36 | u.setDownloadMode(downloadMode); 37 | } 38 | } 39 | } 40 | 41 | public void setFormat(String userId,DownloadFormat downloadFormat){ 42 | for(User u : users){ 43 | if(u.getId().equalsIgnoreCase(userId)){ 44 | u.setDownloadFormat(downloadFormat); 45 | } 46 | } 47 | } 48 | 49 | public boolean isInList(String userId){ 50 | for(User u : users){ 51 | if(u.getId().equalsIgnoreCase(userId)) 52 | return true; 53 | } 54 | return false; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/utils/Curl.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.utils; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | import java.net.MalformedURLException; 10 | import java.net.URL; 11 | import java.nio.charset.StandardCharsets; 12 | 13 | public class Curl { 14 | 15 | private final String link; 16 | private static final Logger logger = LoggerFactory.getLogger(Curl.class); 17 | private final String query; 18 | 19 | public Curl(String url,String query){ 20 | this.link = url; 21 | this.query = URLUTF8Encoder.encode(query); 22 | } 23 | 24 | public String run() throws MalformedURLException { 25 | URL url = new URL(link + query); 26 | StringBuilder builder = new StringBuilder(); 27 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) { 28 | for (String line; (line = reader.readLine()) != null;) { 29 | builder.append(line).append("\n"); 30 | } 31 | } catch (IOException e) { 32 | logger.error(e.getMessage()); 33 | e.printStackTrace(); 34 | } 35 | return builder.toString(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/utils/ReplyKeyboardBuilder.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.utils; 2 | 3 | import org.telegram.telegrambots.meta.api.objects.LoginUrl; 4 | import org.telegram.telegrambots.meta.api.objects.games.CallbackGame; 5 | import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup; 6 | import org.telegram.telegrambots.meta.api.objects.replykeyboard.ReplyKeyboardMarkup; 7 | import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton; 8 | import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.KeyboardButton; 9 | import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.KeyboardRow; 10 | 11 | import java.util.ArrayList; 12 | import java.util.LinkedList; 13 | import java.util.List; 14 | 15 | @SuppressWarnings("unused") 16 | public class ReplyKeyboardBuilder { 17 | 18 | private ReplyKeyboardBuilder() { 19 | } 20 | 21 | public static ReplyKeyboardMarkupBuilder createReply() { 22 | return new ReplyKeyboardMarkupBuilder(); 23 | } 24 | 25 | public static InlineKeyboardMarkupBuilder createInline() { 26 | return new InlineKeyboardMarkupBuilder(); 27 | } 28 | 29 | @SuppressWarnings("unused") 30 | public static class ReplyKeyboardMarkupBuilder { 31 | 32 | private final List keyboard = new ArrayList<>(); 33 | private KeyboardRow row = null; 34 | 35 | ReplyKeyboardMarkupBuilder() { 36 | } 37 | 38 | public ReplyKeyboardMarkupBuilder row() { 39 | if (row != null) { 40 | keyboard.add(row); 41 | } 42 | row = new KeyboardRow(); 43 | return this; 44 | } 45 | 46 | public ReplyKeyboardMarkupBuilder addText(String text) { 47 | row.add(text); 48 | return this; 49 | } 50 | 51 | public ReplyKeyboardMarkupBuilder addRequestContact(String text) { 52 | row.add(new KeyboardButton(text).setRequestContact(true)); 53 | return this; 54 | } 55 | 56 | public ReplyKeyboardMarkupBuilder addRequestLocation(String text) { 57 | row.add(new KeyboardButton(text).setRequestLocation(true)); 58 | return this; 59 | } 60 | 61 | public ReplyKeyboardMarkup build() { 62 | if (row != null) { 63 | keyboard.add(row); 64 | } 65 | return new ReplyKeyboardMarkup() 66 | .setKeyboard(keyboard) 67 | .setResizeKeyboard(true); 68 | } 69 | } 70 | 71 | @SuppressWarnings("unused") 72 | public static class InlineKeyboardMarkupBuilder { 73 | 74 | private final List> keyboard = new ArrayList<>(); 75 | private List row; 76 | 77 | InlineKeyboardMarkupBuilder() { 78 | } 79 | 80 | public InlineKeyboardMarkupBuilder row() { 81 | if (row != null) { 82 | keyboard.add(row); 83 | } 84 | row = new LinkedList<>(); 85 | return this; 86 | } 87 | 88 | public InlineKeyboardMarkupBuilder addUrl(String text, String url) { 89 | row.add(new InlineKeyboardButton(text).setUrl(url)); 90 | return this; 91 | } 92 | 93 | public InlineKeyboardMarkupBuilder addLoginUrl(String text, LoginUrl loginUrl) { 94 | row.add(new InlineKeyboardButton(text).setLoginUrl(loginUrl)); 95 | return this; 96 | } 97 | 98 | public InlineKeyboardMarkupBuilder addLoginUrl(String text, String loginUrl) { 99 | row.add(new InlineKeyboardButton(text).setLoginUrl(new LoginUrl(loginUrl))); 100 | return this; 101 | } 102 | 103 | public InlineKeyboardMarkupBuilder addCallbackData(String text, String callbackData) { 104 | row.add(new InlineKeyboardButton(text).setCallbackData(callbackData)); 105 | return this; 106 | } 107 | 108 | public InlineKeyboardMarkupBuilder addSwitchInlineQuery(String text, String switchInlineQuery) { 109 | row.add(new InlineKeyboardButton(text).setSwitchInlineQuery(switchInlineQuery)); 110 | return this; 111 | } 112 | 113 | public InlineKeyboardMarkupBuilder addsetSwitchInlineQueryCurrentChat(String text, String switchInlineQueryCurrentChat) { 114 | row.add(new InlineKeyboardButton(text).setSwitchInlineQueryCurrentChat(switchInlineQueryCurrentChat)); 115 | return this; 116 | } 117 | 118 | public InlineKeyboardMarkupBuilder addCallbackGame(String text, CallbackGame callbackGame) { 119 | row.add(new InlineKeyboardButton(text).setCallbackGame(callbackGame)); 120 | return this; 121 | } 122 | 123 | public InlineKeyboardMarkupBuilder addPay(String text, boolean pay) { 124 | row.add(new InlineKeyboardButton(text).setPay(pay)); 125 | return this; 126 | } 127 | 128 | public InlineKeyboardMarkup build() { 129 | return new InlineKeyboardMarkup() 130 | .setKeyboard(keyboard); 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/utils/URLUTF8Encoder.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.utils; 2 | 3 | public class URLUTF8Encoder 4 | { 5 | final static String[] hex = { 6 | "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", 7 | "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", 8 | "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17", 9 | "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f", 10 | "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27", 11 | "%28", "%29", "%2a", "%2b", "%2c", "%2d", "%2e", "%2f", 12 | "%30", "%31", "%32", "%33", "%34", "%35", "%36", "%37", 13 | "%38", "%39", "%3a", "%3b", "%3c", "%3d", "%3e", "%3f", 14 | "%40", "%41", "%42", "%43", "%44", "%45", "%46", "%47", 15 | "%48", "%49", "%4a", "%4b", "%4c", "%4d", "%4e", "%4f", 16 | "%50", "%51", "%52", "%53", "%54", "%55", "%56", "%57", 17 | "%58", "%59", "%5a", "%5b", "%5c", "%5d", "%5e", "%5f", 18 | "%60", "%61", "%62", "%63", "%64", "%65", "%66", "%67", 19 | "%68", "%69", "%6a", "%6b", "%6c", "%6d", "%6e", "%6f", 20 | "%70", "%71", "%72", "%73", "%74", "%75", "%76", "%77", 21 | "%78", "%79", "%7a", "%7b", "%7c", "%7d", "%7e", "%7f", 22 | "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87", 23 | "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f", 24 | "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", 25 | "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f", 26 | "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7", 27 | "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af", 28 | "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7", 29 | "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf", 30 | "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7", 31 | "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf", 32 | "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7", 33 | "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df", 34 | "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7", 35 | "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef", 36 | "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7", 37 | "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff" 38 | }; 39 | public static String encode(String s) 40 | { 41 | StringBuilder sbuf = new StringBuilder(); 42 | int len = s.length(); 43 | for (int i = 0; i < len; i++) { 44 | int ch = s.charAt(i); 45 | if ('A' <= ch && ch <= 'Z') { // 'A'..'Z' 46 | sbuf.append((char)ch); 47 | } else if ('a' <= ch && ch <= 'z') { // 'a'..'z' 48 | sbuf.append((char)ch); 49 | } else if ('0' <= ch && ch <= '9') { // '0'..'9' 50 | sbuf.append((char)ch); 51 | } else if (ch == ' ') { // space 52 | sbuf.append('+'); 53 | } else if (ch == '-' || ch == '_' // unreserved 54 | || ch == '.' || ch == '!' 55 | || ch == '~' || ch == '*' 56 | || ch == '\'' || ch == '(' 57 | || ch == ')') { 58 | sbuf.append((char)ch); 59 | } else if (ch <= 0x007f) { // other ASCII 60 | sbuf.append(hex[ch]); 61 | } else if (ch <= 0x07FF) { // non-ASCII <= 0x7FF 62 | sbuf.append(hex[0xc0 | (ch >> 6)]); 63 | sbuf.append(hex[0x80 | (ch & 0x3F)]); 64 | } else { // 0x7FF < ch <= 0xFFFF 65 | sbuf.append(hex[0xe0 | (ch >> 12)]); 66 | sbuf.append(hex[0x80 | ((ch >> 6) & 0x3F)]); 67 | sbuf.append(hex[0x80 | (ch & 0x3F)]); 68 | } 69 | } 70 | return sbuf.toString(); 71 | } 72 | } -------------------------------------------------------------------------------- /src/main/java/me/reply/deemixbot/utils/UpdateUserlistRunnable.java: -------------------------------------------------------------------------------- 1 | package me.reply.deemixbot.utils; 2 | 3 | import com.google.gson.Gson; 4 | import me.reply.deemixbot.bot.Bot; 5 | import me.reply.deemixbot.bot.Config; 6 | import org.apache.commons.io.FileUtils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.nio.charset.StandardCharsets; 13 | 14 | public class UpdateUserlistRunnable implements Runnable{ 15 | 16 | private final Config c; 17 | private static final Logger logger = LoggerFactory.getLogger(UpdateUserlistRunnable.class); 18 | private volatile boolean run; 19 | 20 | public UpdateUserlistRunnable(Config c){ 21 | this.c = c; 22 | run = true; 23 | } 24 | 25 | @Override 26 | public void run() { 27 | if(c.getSave_users_list_cooldown() <= 0){ 28 | logger.info("Save users cooldown is set to " + c.getSave_users_list_cooldown() + ", exiting thread..."); 29 | return; 30 | } 31 | while(run){ 32 | try { 33 | Thread.sleep(c.getSave_users_list_cooldown()); 34 | } catch (InterruptedException e) { 35 | logger.error(e.getMessage()); 36 | if(c.isDebug_mode()) 37 | e.printStackTrace(); 38 | } 39 | Gson g = new Gson(); 40 | int i = 0; 41 | File f; 42 | do{ 43 | f = new File("users" + i + ".json"); 44 | i++; 45 | } 46 | while (f.exists()); 47 | 48 | String json = g.toJson(Bot.getInstance().getUserManager()); 49 | try { 50 | FileUtils.write(f,json, StandardCharsets.UTF_8); 51 | } catch (IOException e) { 52 | logger.error(e.getMessage()); 53 | if(c.isDebug_mode()) 54 | e.printStackTrace(); 55 | } 56 | } 57 | } 58 | 59 | private void stopThread(){ 60 | run = false; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # Direct log messages to a log file 2 | log4j.appender.file=org.apache.log4j.FileAppender 3 | log4j.appender.file.File=log.txt 4 | log4j.appender.file.layout=org.apache.log4j.PatternLayout 5 | log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n 6 | 7 | # Root logger option 8 | log4j.rootLogger=INFO, file -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | java -Dfile.encoding=UTF-8 --add-opens java.base/java.lang=ALL-UNNAMED -jar $1 --------------------------------------------------------------------------------