├── .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
--------------------------------------------------------------------------------