├── .gitignore
├── icon.ico
├── splash.png
├── org.json.jar
├── fluent-hc-4.3.3.jar
├── httpclient-4.3.3.jar
├── httpcore-4.3.2.jar
├── httpmime-4.3.3.jar
├── commons-codec-1.6.jar
├── commons-logging-1.1.3.jar
├── httpclient-cache-4.3.3.jar
├── dist
└── thumbnailguihacker2.1.1.jar
├── src
└── apu
│ └── scratch
│ └── hax
│ ├── watermark.png
│ ├── ProgressFileEntity.java
│ ├── GuiHackerProjectsView.java
│ ├── GuiHackerLogin.java
│ ├── GuiHackerBackend.java
│ ├── ThumbnailGuiHacker.java
│ └── GuiHackerFileChooser.java
├── Readme.md
├── .project
├── LICENSE
├── .settings
└── org.eclipse.jdt.core.prefs
└── .classpath
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | build/
3 | launch4j.xml
--------------------------------------------------------------------------------
/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MegaApuTurkUltra/Scratch-Thumbnail-Changer/HEAD/icon.ico
--------------------------------------------------------------------------------
/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MegaApuTurkUltra/Scratch-Thumbnail-Changer/HEAD/splash.png
--------------------------------------------------------------------------------
/org.json.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MegaApuTurkUltra/Scratch-Thumbnail-Changer/HEAD/org.json.jar
--------------------------------------------------------------------------------
/fluent-hc-4.3.3.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MegaApuTurkUltra/Scratch-Thumbnail-Changer/HEAD/fluent-hc-4.3.3.jar
--------------------------------------------------------------------------------
/httpclient-4.3.3.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MegaApuTurkUltra/Scratch-Thumbnail-Changer/HEAD/httpclient-4.3.3.jar
--------------------------------------------------------------------------------
/httpcore-4.3.2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MegaApuTurkUltra/Scratch-Thumbnail-Changer/HEAD/httpcore-4.3.2.jar
--------------------------------------------------------------------------------
/httpmime-4.3.3.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MegaApuTurkUltra/Scratch-Thumbnail-Changer/HEAD/httpmime-4.3.3.jar
--------------------------------------------------------------------------------
/commons-codec-1.6.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MegaApuTurkUltra/Scratch-Thumbnail-Changer/HEAD/commons-codec-1.6.jar
--------------------------------------------------------------------------------
/commons-logging-1.1.3.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MegaApuTurkUltra/Scratch-Thumbnail-Changer/HEAD/commons-logging-1.1.3.jar
--------------------------------------------------------------------------------
/httpclient-cache-4.3.3.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MegaApuTurkUltra/Scratch-Thumbnail-Changer/HEAD/httpclient-cache-4.3.3.jar
--------------------------------------------------------------------------------
/dist/thumbnailguihacker2.1.1.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MegaApuTurkUltra/Scratch-Thumbnail-Changer/HEAD/dist/thumbnailguihacker2.1.1.jar
--------------------------------------------------------------------------------
/src/apu/scratch/hax/watermark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MegaApuTurkUltra/Scratch-Thumbnail-Changer/HEAD/src/apu/scratch/hax/watermark.png
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # MegaApuTurkUltra's Scratch Thumbnail Changer #
2 | Allows arbitrary thumbnails (of any supported image format including GIF) to be set for projects on [MIT Scratch](https://scratch.mit.edu).
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | Thumbnail GUI Hacker
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.jdt.core.javanature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | ======= The MegaApuTurkUltra Swag License =======
2 |
3 | >>> Conditions for use of this software: <<<
4 |
5 | * You MAY NOT use this software for evil,
6 | including but not limited to purposes of
7 | malicious intent, or gaining access to systems
8 | you do not have permission to access.
9 |
10 | If you do not wish to comply with these terms, go
11 | use some other software please (or forge requests
12 | yourself)
13 |
14 | =================================================
--------------------------------------------------------------------------------
/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
5 | org.eclipse.jdt.core.compiler.compliance=1.7
6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate
7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate
8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate
9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
11 | org.eclipse.jdt.core.compiler.source=1.7
12 |
--------------------------------------------------------------------------------
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/apu/scratch/hax/ProgressFileEntity.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package apu.scratch.hax;
5 |
6 | import java.io.File;
7 | import java.io.IOException;
8 | import java.io.OutputStream;
9 |
10 | import org.apache.http.entity.ContentType;
11 |
12 | /**
13 | * @see http://stackoverflow.com/a/8475006/1021196
14 | */
15 | public class ProgressFileEntity extends org.apache.http.entity.FileEntity {
16 |
17 | public static interface ProgressCallback {
18 | public void updateProgress(int progress);
19 | }
20 |
21 | class OutputStreamProgress extends OutputStream {
22 | private final OutputStream outstream;
23 | private volatile long bytesWritten = 0;
24 | private ProgressCallback callback;
25 | private ProgressFileEntity entity;
26 |
27 | public OutputStreamProgress(OutputStream outstream,
28 | ProgressCallback callback, ProgressFileEntity entity) {
29 | this.outstream = outstream;
30 | this.callback = callback;
31 | this.entity = entity;
32 | }
33 |
34 | @Override
35 | public void write(int b) throws IOException {
36 | outstream.write(b);
37 | bytesWritten++;
38 | updateProgress();
39 | }
40 |
41 | @Override
42 | public void write(byte[] b) throws IOException {
43 | outstream.write(b);
44 | bytesWritten += b.length;
45 | updateProgress();
46 | }
47 |
48 | @Override
49 | public void write(byte[] b, int off, int len) throws IOException {
50 | outstream.write(b, off, len);
51 | bytesWritten += len;
52 | updateProgress();
53 | }
54 |
55 | @Override
56 | public void flush() throws IOException {
57 | outstream.flush();
58 | }
59 |
60 | @Override
61 | public void close() throws IOException {
62 | outstream.close();
63 | }
64 |
65 | public long getWrittenLength() {
66 | return bytesWritten;
67 | }
68 |
69 | private void updateProgress() {
70 | callback.updateProgress((int) (100 * bytesWritten / entity
71 | .getContentLength()));
72 | }
73 | }
74 |
75 | private OutputStreamProgress outstream;
76 | private ProgressCallback callback;
77 |
78 | public ProgressFileEntity(File file, String contentType,
79 | ProgressCallback callback) {
80 | super(file, ContentType.create(contentType));
81 | this.callback = callback;
82 | }
83 |
84 | @Override
85 | public void writeTo(OutputStream outstream) throws IOException {
86 | this.outstream = new OutputStreamProgress(outstream, callback, this);
87 | super.writeTo(this.outstream);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/apu/scratch/hax/GuiHackerProjectsView.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package apu.scratch.hax;
5 |
6 | import java.awt.BorderLayout;
7 | import java.awt.event.ActionEvent;
8 | import java.awt.event.ActionListener;
9 |
10 | import javax.swing.AbstractListModel;
11 | import javax.swing.DefaultListModel;
12 | import javax.swing.JButton;
13 | import javax.swing.JLabel;
14 | import javax.swing.JList;
15 | import javax.swing.JPanel;
16 | import javax.swing.JScrollPane;
17 | import javax.swing.ListSelectionModel;
18 |
19 | /**
20 | * @author "MegaApuTurkUltra"
21 | *
22 | */
23 | public class GuiHackerProjectsView extends JPanel {
24 | public static class Project {
25 | public String title;
26 | public int id;
27 |
28 | public String toString() {
29 | return title;
30 | }
31 |
32 | public Project(String title, int id) {
33 | this.title = title;
34 | this.id = id;
35 | }
36 | }
37 |
38 | private static final long serialVersionUID = -8777426689312871264L;
39 | public JList list;
40 | public DefaultListModel listModel;
41 | public JButton btnChangeIt;
42 | private JScrollPane scrollPane;
43 |
44 | /**
45 | * Create the panel.
46 | */
47 | public GuiHackerProjectsView() {
48 | setLayout(new BorderLayout(0, 0));
49 |
50 | JLabel lblSelectAProject = new JLabel(
51 | "Select a project to change its thumbnail");
52 | add(lblSelectAProject, BorderLayout.NORTH);
53 |
54 | btnChangeIt = new JButton("Change it!");
55 | btnChangeIt.addActionListener(new ActionListener() {
56 | public void actionPerformed(ActionEvent e) {
57 | if (list.getSelectedIndex() == -1)
58 | return;
59 | Project proj = list.getSelectedValue();
60 | ThumbnailGuiHacker.INSTANCE.goToFileChooser(proj);
61 | }
62 | });
63 | btnChangeIt.setEnabled(false);
64 | add(btnChangeIt, BorderLayout.SOUTH);
65 | listModel = new DefaultListModel();
66 |
67 | scrollPane = new JScrollPane();
68 | add(scrollPane, BorderLayout.CENTER);
69 |
70 | list = new JList();
71 | list.setEnabled(false);
72 | list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
73 | list.setModel(new AbstractListModel() {
74 | private static final long serialVersionUID = -8079045657394409854L;
75 | @Override
76 | public int getSize() {
77 | return 1;
78 | }
79 | @Override
80 | public Project getElementAt(int index) {
81 | return new Project("Loading...", -1);
82 | }
83 | });
84 | scrollPane.setViewportView(list);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/apu/scratch/hax/GuiHackerLogin.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package apu.scratch.hax;
5 |
6 | import java.awt.Font;
7 | import java.awt.GridBagConstraints;
8 | import java.awt.GridBagLayout;
9 | import java.awt.Insets;
10 | import java.awt.event.ActionEvent;
11 | import java.awt.event.ActionListener;
12 |
13 | import javax.swing.JButton;
14 | import javax.swing.JLabel;
15 | import javax.swing.JOptionPane;
16 | import javax.swing.JPanel;
17 | import javax.swing.JPasswordField;
18 | import javax.swing.JTextArea;
19 | import javax.swing.JTextField;
20 | import javax.swing.SwingUtilities;
21 | import javax.swing.UIManager;
22 |
23 | /**
24 | * Login screen
25 | *
26 | * @author "MegaApuTurkUltra"
27 | *
28 | */
29 | public class GuiHackerLogin extends JPanel {
30 | private static final long serialVersionUID = -6357875066223412629L;
31 | private JTextField username;
32 | private JPasswordField password;
33 | private JButton btnLogIn;
34 |
35 | /**
36 | * Create the panel.
37 | */
38 | public GuiHackerLogin() {
39 | GridBagLayout gridBagLayout = new GridBagLayout();
40 | gridBagLayout.columnWidths = new int[] { 0, 0, 0 };
41 | gridBagLayout.rowHeights = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
42 | gridBagLayout.columnWeights = new double[] { 1.0, 1.0, Double.MIN_VALUE };
43 | gridBagLayout.rowWeights = new double[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
44 | 1.0, 0.0, Double.MIN_VALUE };
45 | setLayout(gridBagLayout);
46 |
47 | JLabel lblLogInTo = new JLabel("Log in to your Scratch Account");
48 | lblLogInTo.setFont(new Font("Tahoma", Font.PLAIN, 20));
49 | GridBagConstraints gbc_lblLogInTo = new GridBagConstraints();
50 | gbc_lblLogInTo.gridwidth = 2;
51 | gbc_lblLogInTo.insets = new Insets(0, 0, 5, 0);
52 | gbc_lblLogInTo.gridx = 0;
53 | gbc_lblLogInTo.gridy = 0;
54 | add(lblLogInTo, gbc_lblLogInTo);
55 |
56 | JLabel lblThumbnailHacker = new JLabel("Thumbnail Hacker - A swaggy program by MegaApuTurkUltra");
57 | GridBagConstraints gbc_lblThumbnailHacker = new GridBagConstraints();
58 | gbc_lblThumbnailHacker.gridwidth = 2;
59 | gbc_lblThumbnailHacker.insets = new Insets(0, 0, 5, 5);
60 | gbc_lblThumbnailHacker.gridx = 0;
61 | gbc_lblThumbnailHacker.gridy = 1;
62 | add(lblThumbnailHacker, gbc_lblThumbnailHacker);
63 |
64 | JLabel lblUsername = new JLabel("Username:");
65 | GridBagConstraints gbc_lblUsername = new GridBagConstraints();
66 | gbc_lblUsername.anchor = GridBagConstraints.EAST;
67 | gbc_lblUsername.insets = new Insets(0, 0, 5, 5);
68 | gbc_lblUsername.gridx = 0;
69 | gbc_lblUsername.gridy = 2;
70 | add(lblUsername, gbc_lblUsername);
71 |
72 | username = new JTextField();
73 | GridBagConstraints gbc_username = new GridBagConstraints();
74 | gbc_username.insets = new Insets(0, 0, 5, 0);
75 | gbc_username.fill = GridBagConstraints.HORIZONTAL;
76 | gbc_username.gridx = 1;
77 | gbc_username.gridy = 2;
78 | add(username, gbc_username);
79 | username.setColumns(10);
80 |
81 | JLabel lblPassword = new JLabel("Password:");
82 | GridBagConstraints gbc_lblPassword = new GridBagConstraints();
83 | gbc_lblPassword.anchor = GridBagConstraints.EAST;
84 | gbc_lblPassword.insets = new Insets(0, 0, 5, 5);
85 | gbc_lblPassword.gridx = 0;
86 | gbc_lblPassword.gridy = 3;
87 | add(lblPassword, gbc_lblPassword);
88 |
89 | password = new JPasswordField();
90 | GridBagConstraints gbc_password = new GridBagConstraints();
91 | gbc_password.insets = new Insets(0, 0, 5, 0);
92 | gbc_password.fill = GridBagConstraints.HORIZONTAL;
93 | gbc_password.gridx = 1;
94 | gbc_password.gridy = 3;
95 | add(password, gbc_password);
96 |
97 | btnLogIn = new JButton("Log In");
98 | btnLogIn.addActionListener(new ActionListener() {
99 | public void actionPerformed(ActionEvent e) {
100 | SwingUtilities.invokeLater(new Runnable() {
101 | @Override
102 | public void run() {
103 | username.setEnabled(false);
104 | password.setEnabled(false);
105 | btnLogIn.setEnabled(false);
106 | }
107 | });
108 | new Thread(new Runnable(){
109 | @Override
110 | public void run() {
111 | boolean success = ThumbnailGuiHacker.INSTANCE
112 | .initAndLogin(username.getText(),
113 | password.getPassword());
114 | boolean doCont = success;
115 | if (!success) {
116 | doCont = JOptionPane.showConfirmDialog(
117 | ThumbnailGuiHacker.INSTANCE,
118 | "Login might have failed. Continue?",
119 | "Message", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION;
120 | }
121 | if (!doCont) {
122 | username.setEnabled(true);
123 | password.setEnabled(true);
124 | btnLogIn.setEnabled(true);
125 | } else {
126 | ThumbnailGuiHacker.INSTANCE.goToProjects();
127 | }
128 | }
129 | }).start();
130 | }
131 | });
132 | btnLogIn.setFont(new Font("Tahoma", Font.PLAIN, 20));
133 | GridBagConstraints gbc_btnLogIn = new GridBagConstraints();
134 | gbc_btnLogIn.insets = new Insets(0, 0, 5, 0);
135 | gbc_btnLogIn.fill = GridBagConstraints.BOTH;
136 | gbc_btnLogIn.gridx = 1;
137 | gbc_btnLogIn.gridy = 5;
138 | add(btnLogIn, gbc_btnLogIn);
139 |
140 | JTextArea txtrNoteYourLogin = new JTextArea();
141 | txtrNoteYourLogin.setFont(new Font("Monospaced", Font.ITALIC, 13));
142 | txtrNoteYourLogin
143 | .setText("Note: Your login info is not collected in any way. It is discarded once this application closes");
144 | txtrNoteYourLogin.setBackground(UIManager.getColor("Panel.background"));
145 | txtrNoteYourLogin.setEnabled(false);
146 | txtrNoteYourLogin.setEditable(false);
147 | txtrNoteYourLogin.setWrapStyleWord(true);
148 | txtrNoteYourLogin.setLineWrap(true);
149 | GridBagConstraints gbc_txtrNoteYourLogin = new GridBagConstraints();
150 | gbc_txtrNoteYourLogin.insets = new Insets(0, 0, 5, 0);
151 | gbc_txtrNoteYourLogin.gridwidth = 2;
152 | gbc_txtrNoteYourLogin.fill = GridBagConstraints.BOTH;
153 | gbc_txtrNoteYourLogin.gridx = 0;
154 | gbc_txtrNoteYourLogin.gridy = 6;
155 | add(txtrNoteYourLogin, gbc_txtrNoteYourLogin);
156 |
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/apu/scratch/hax/GuiHackerBackend.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package apu.scratch.hax;
5 |
6 | import java.io.File;
7 | import java.io.InputStream;
8 |
9 | import javax.net.ssl.SSLContext;
10 | import javax.net.ssl.TrustManager;
11 | import javax.net.ssl.X509TrustManager;
12 |
13 | import org.apache.http.HttpHost;
14 | import org.apache.http.client.CookieStore;
15 | import org.apache.http.client.config.CookieSpecs;
16 | import org.apache.http.client.config.RequestConfig;
17 | import org.apache.http.client.methods.CloseableHttpResponse;
18 | import org.apache.http.client.methods.HttpUriRequest;
19 | import org.apache.http.client.methods.RequestBuilder;
20 | import org.apache.http.cookie.Cookie;
21 | import org.apache.http.entity.StringEntity;
22 | import org.apache.http.impl.client.BasicCookieStore;
23 | import org.apache.http.impl.client.CloseableHttpClient;
24 | import org.apache.http.impl.client.HttpClientBuilder;
25 | import org.apache.http.impl.client.HttpClients;
26 | import org.apache.http.impl.cookie.BasicClientCookie;
27 | import org.json.JSONArray;
28 | import org.json.JSONObject;
29 |
30 | import apu.scratch.hax.ProgressFileEntity.ProgressCallback;
31 |
32 | /**
33 | * Backend to handle HTTP
34 | *
35 | * @author "MegaApuTurkUltra"
36 | */
37 | public class GuiHackerBackend {
38 | static RequestConfig globalConfig;
39 | static CookieStore cookieStore;
40 | static CloseableHttpClient httpClient;
41 | static CloseableHttpResponse resp;
42 | static String csrfToken;
43 |
44 | private static boolean useProxy = false;
45 |
46 | public static void reset() {
47 | try {
48 | csrfToken = null;
49 | if (cookieStore != null)
50 | cookieStore.clear();
51 | if (resp != null)
52 | resp.close();
53 | if (httpClient != null)
54 | httpClient.close();
55 | } catch (Exception e) {
56 | e.printStackTrace();
57 | // ignore. HttpClient was probably in the middle of something
58 | // or not fully initialized. We can let GC handle this
59 | }
60 | System.gc();
61 | }
62 |
63 | /**
64 | * DO NOT USE SUPER DANGEROUS
65 | */
66 | public static SSLContext bypassSSLCertCheck() throws Exception {
67 | /*SSLContext sslContext = SSLContext.getInstance("SSL");
68 | sslContext.init(null, new TrustManager[] { new X509TrustManager() {
69 | public X509Certificate[] getAcceptedIssuers() {
70 | return null;
71 | }
72 |
73 | public void checkClientTrusted(X509Certificate[] certs,
74 | String authType) {
75 | }
76 |
77 | public void checkServerTrusted(X509Certificate[] certs,
78 | String authType) {
79 | }
80 | } }, new SecureRandom());
81 | return sslContext;*/ return null;
82 | }
83 |
84 | public static void init() throws Exception {
85 | RequestConfig.Builder configBuilder = RequestConfig.custom()
86 | .setCookieSpec(CookieSpecs.BROWSER_COMPATIBILITY)
87 | .setSocketTimeout(0).setConnectionRequestTimeout(0)
88 | .setConnectTimeout(0);
89 | if (useProxy) {
90 | configBuilder.setProxy(new HttpHost("localhost", 8888));
91 | }
92 |
93 | globalConfig = configBuilder.build();
94 |
95 | if (!useProxy) {
96 | cookieStore = new BasicCookieStore();
97 | }
98 | BasicClientCookie lang = new BasicClientCookie("scratchlanguage", "en");
99 | lang.setDomain(".scratch.mit.edu");
100 | lang.setPath("/");
101 | cookieStore.addCookie(lang);
102 | // hacks activated in 3...2...1..
103 |
104 | HttpClientBuilder clientBuilder = HttpClients
105 | .custom()
106 | .setDefaultRequestConfig(globalConfig)
107 | .setUserAgent(
108 | "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36")
109 | .setDefaultCookieStore(cookieStore);
110 | if (useProxy) {
111 | clientBuilder.setSslcontext(bypassSSLCertCheck());
112 | }
113 | httpClient = clientBuilder.build();
114 | }
115 |
116 | public static void login(String username, char[] password) throws Exception {
117 | System.out.print("Requesting index page...");
118 | HttpUriRequest index = RequestBuilder.get()
119 | .setUri("https://scratch.mit.edu/")
120 | .addHeader("Accept", "text/html")
121 | .addHeader("Referer", "https://scratch.mit.edu").build();
122 | resp = httpClient.execute(index);
123 | System.out.println(resp.getStatusLine());
124 | resp.close();
125 |
126 | System.out.print("Requesting CSRF token...");
127 | HttpUriRequest csrf = RequestBuilder.get()
128 | .setUri("https://scratch.mit.edu/csrf_token/")
129 | .addHeader("Accept", "*/*")
130 | .addHeader("Referer", "https://scratch.mit.edu")
131 | .addHeader("X-Requested-With", "XMLHttpRequest").build();
132 | resp = httpClient.execute(csrf);
133 | System.out.println(resp.getStatusLine());
134 | resp.close();
135 |
136 | for (Cookie c : cookieStore.getCookies()) {
137 | if (c.getName().equals("scratchcsrftoken")) {
138 | csrfToken = c.getValue();
139 | }
140 | }
141 | System.out.println("Found CSRF: " + csrfToken);
142 |
143 | System.out.print("Logging in...");
144 | JSONObject loginObj = new JSONObject();
145 | loginObj.put("username", username);
146 | loginObj.put("password", new String(password));
147 | loginObj.put("captcha_challenge", "");
148 | loginObj.put("captcha_response", "");
149 | loginObj.put("embed_captcha", false);
150 | loginObj.put("timezone", "America/New_York");
151 | loginObj.put("csrfmiddlewaretoken", csrfToken);
152 | HttpUriRequest login = RequestBuilder
153 | .post()
154 | .setUri("https://scratch.mit.edu/login/")
155 | .addHeader("Accept",
156 | "application/json, text/javascript, */*; q=0.01")
157 | .addHeader("Referer", "https://scratch.mit.edu")
158 | .addHeader("Origin", "https://scratch.mit.edu")
159 | .addHeader("Content-Type", "application/json")
160 | .addHeader("X-Requested-With", "XMLHttpRequest")
161 | .addHeader("X-CSRFToken", csrfToken)
162 | .setEntity(new StringEntity(loginObj.toString())).build();
163 | resp = httpClient.execute(login);
164 | System.out.println(resp.getStatusLine());
165 | StringBuffer loginResp = new StringBuffer();
166 | InputStream in = resp.getEntity().getContent();
167 | int i;
168 | while ((i = in.read()) != -1)
169 | loginResp.append((char) i);
170 | in.close();
171 | resp.close();
172 | System.out.println(loginResp.toString());
173 |
174 | JSONArray response = new JSONArray(loginResp.toString());
175 | JSONObject obj = response.getJSONObject(0);
176 | String loginMessage = "Unknown";
177 | if (obj.has("msg"))
178 | loginMessage = obj.getString("msg");
179 | if (obj.getInt("success") != 1) {
180 | throw new Exception(
181 | "Login might have failed\nScratch returned the message:\n\""
182 | + loginMessage + "\"");
183 | }
184 | }
185 |
186 | public static JSONArray getProjects() throws Exception {
187 | System.out.print("Loading projects...");
188 | HttpUriRequest projects = RequestBuilder.get()
189 | .setUri("https://scratch.mit.edu/site-api/projects/all/")
190 | .addHeader("Accept", "*/*")
191 | .addHeader("Referer", "https://scratch.mit.edu")
192 | .addHeader("X-Requested-With", "XMLHttpRequest").build();
193 | resp = httpClient.execute(projects);
194 | System.out.println(resp.getStatusLine());
195 | resp.close();
196 | StringBuffer projectsResp = new StringBuffer();
197 | InputStream in = resp.getEntity().getContent();
198 | int i;
199 | while ((i = in.read()) != -1)
200 | projectsResp.append((char) i);
201 | in.close();
202 | resp.close();
203 | return new JSONArray(projectsResp.toString());
204 | }
205 |
206 | public static void hackThumbnail(int projectId, File thumbnail,
207 | String mimeType) throws Exception {
208 | System.out.print("Uploading thumbnail...");
209 |
210 | // debug only, do not enable
211 | // useProxy = true;
212 | // init();
213 |
214 | // the ST is sneaky :P
215 | BasicClientCookie pid = new BasicClientCookie("projectId",
216 | Integer.toString(projectId));
217 | pid.setDomain(".scratch.mit.edu");
218 | pid.setPath("/");
219 | cookieStore.addCookie(pid);
220 |
221 | // even more sneaky...
222 | System.out.print("Refinding CSRF...");
223 | for (Cookie c : cookieStore.getCookies()) {
224 | if (c.getName().equals("scratchcsrftoken")) {
225 | csrfToken = c.getValue();
226 | }
227 | }
228 | System.out.println("Found CSRF: " + csrfToken);
229 |
230 | HttpUriRequest thumb = RequestBuilder
231 | .post()
232 | .setUri("https://scratch.mit.edu/internalapi/project"
233 | + "/thumbnail/" + projectId + "/set/?v=v440.3&_rnd="
234 | + Math.random())
235 | .addHeader(
236 | "Referer",
237 | "https://cdn.scratch.mit.edu/scratchr2/static/__61560e08176805f6f84b8d1dd737d59b__/Scratch.swf")
238 | .addHeader("Content-Type", mimeType)
239 | .addHeader("X-CSRFToken", csrfToken)
240 | .addHeader("X-Requested-With", "ShockwaveFlash/19.0.0.226")
241 | .addHeader("Origin", "https://cdn.scratch.mit.edu")
242 | .addHeader("Accept", "*/*")
243 | .addHeader("Accept-Language", "en-US,en;q=0.8")
244 | .setEntity(
245 | new ProgressFileEntity(thumbnail, "image/png",
246 | new ProgressCallback() {
247 | @Override
248 | public void updateProgress(int progress) {
249 | if(ThumbnailGuiHacker.INSTANCE == null){
250 | System.out.print("\rUpload: " + progress + "%");
251 | return;
252 | }
253 | ThumbnailGuiHacker.INSTANCE
254 | .setProgress(progress);
255 | }
256 | })).build();
257 | resp = httpClient.execute(thumb);
258 | System.out.println(resp.getStatusLine());
259 | int statusCode = resp.getStatusLine().getStatusCode();
260 | InputStream in = resp.getEntity().getContent();
261 | int i;
262 | while ((i = in.read()) != -1) {
263 | System.out.print((char) i);
264 | }
265 | resp.close();
266 | if (statusCode != 200) {
267 | throw new IllegalStateException("Response status is " + statusCode);
268 | }
269 | }
270 | }
271 |
--------------------------------------------------------------------------------
/src/apu/scratch/hax/ThumbnailGuiHacker.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package apu.scratch.hax;
5 |
6 | import java.awt.BorderLayout;
7 | import java.awt.Dialog.ModalityType;
8 | import java.awt.Window;
9 | import java.io.File;
10 | import java.io.PrintWriter;
11 | import java.io.StringWriter;
12 | import java.lang.Thread.UncaughtExceptionHandler;
13 | import java.lang.reflect.InvocationTargetException;
14 |
15 | import javax.swing.JDialog;
16 | import javax.swing.JFrame;
17 | import javax.swing.JOptionPane;
18 | import javax.swing.JPanel;
19 | import javax.swing.JProgressBar;
20 | import javax.swing.SwingUtilities;
21 | import javax.swing.UIManager;
22 | import javax.swing.border.EmptyBorder;
23 |
24 | import org.json.JSONArray;
25 | import org.json.JSONObject;
26 |
27 | import apu.scratch.hax.GuiHackerProjectsView.Project;
28 |
29 | /**
30 | * A GUI hacker for changing thumbnails
31 | *
32 | * @author "MegaApuTurkUltra"
33 | *
34 | */
35 | public class ThumbnailGuiHacker extends JFrame {
36 | private static final long serialVersionUID = 478186628181192812L;
37 | private JPanel contentPane;
38 | private GuiHackerLogin login;
39 | private GuiHackerProjectsView projects;
40 | private GuiHackerFileChooser chooser;
41 | public static volatile ThumbnailGuiHacker INSTANCE;
42 | public static Project selectedProject;
43 | static JDialog loading;
44 | static JProgressBar loadingP;
45 | static boolean guiMode;
46 |
47 | static {
48 | Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
49 | @Override
50 | public void uncaughtException(Thread t, Throwable e) {
51 | System.err.println("Uncaught exception on thread: " + t);
52 | e.printStackTrace();
53 | if (ThumbnailGuiHacker.INSTANCE != null) {
54 | ThumbnailGuiHacker.INSTANCE.showExceptionDialog(e);
55 | }
56 | }
57 | });
58 | }
59 |
60 | /**
61 | * Launch the application.
62 | */
63 | public static void main(String[] args) {
64 | if(args.length == 0){
65 | guiMode = true;
66 | guiMain();
67 | return;
68 | }
69 | guiMode = false;
70 | String username = null, password = null, projectId = null, file = null;
71 | for(int i = 0; i + 1 < args.length; i++){
72 | if(args[i].equals("--username")){
73 | username = args[i + 1];
74 | } else if(args[i].equals("--password")){
75 | password = args[i + 1];
76 | } else if(args[i].equals("--projectId")){
77 | projectId = args[i + 1];
78 | } else if(args[i].equals("--file")){
79 | file = args[i + 1];
80 | }
81 | }
82 | if(username == null || password == null || projectId == null || file == null){
83 | System.out.println("Usage: java -jar thumbnailguihacker.jar --username --password --projectId --file ");
84 | System.exit(-1);
85 | }
86 |
87 | GuiHackerBackend.reset();
88 | try {
89 | GuiHackerBackend.init();
90 | GuiHackerBackend.login(username, password.toCharArray());
91 |
92 | String mime = "image/png";
93 | String ext = file.substring(file.lastIndexOf('.') + 1);
94 | if(ext.equals("gif")){
95 | mime = "image/gif";
96 | } else if(ext.equals("jpg") || ext.equals("jpeg")){
97 | mime = "image/jpeg";
98 | }
99 |
100 | GuiHackerBackend.hackThumbnail(Integer.parseInt(projectId),
101 | new File(file), mime);
102 | System.out.println("\nDone");
103 | } catch (Exception e) {
104 | e.printStackTrace();
105 | }
106 | }
107 |
108 | public static void guiMain(){
109 | try {
110 | UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
111 |
112 | SwingUtilities.invokeAndWait(new Runnable() {
113 | @Override
114 | public void run() {
115 | loading = new JDialog(null,
116 | "Super swaggy hacks in progress...",
117 | ModalityType.MODELESS);
118 | loading.setResizable(false);
119 | loading.setLayout(new BorderLayout());
120 | loading.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
121 | loadingP = new JProgressBar();
122 | loadingP.setIndeterminate(true);
123 | loading.add(loadingP, BorderLayout.CENTER);
124 | loading.setSize(300, 60);
125 | loading.setLocationRelativeTo(null);
126 | loading.setType(Window.Type.UTILITY);
127 | loading.setVisible(true);
128 | }
129 | });
130 | } catch (Exception e) {
131 | e.printStackTrace();
132 | }
133 | try {
134 | Thread.sleep(500);
135 | } catch (InterruptedException e1) {
136 | e1.printStackTrace();
137 | }
138 | SwingUtilities.invokeLater(new Runnable() {
139 | public void run() {
140 | try {
141 | INSTANCE = new ThumbnailGuiHacker();
142 | loading.setVisible(false);
143 | INSTANCE.setVisible(true);
144 | } catch (Exception e) {
145 | e.printStackTrace();
146 | }
147 | }
148 | });
149 | }
150 |
151 | /**
152 | * Create the frame.
153 | */
154 | public ThumbnailGuiHacker() {
155 | setTitle("MegaApuTurkUltra - Thumbnail GUI Hacker");
156 | setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
157 | setSize(600, 600);
158 | contentPane = new JPanel();
159 | contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
160 | contentPane.setLayout(new BorderLayout(0, 0));
161 | setContentPane(contentPane);
162 |
163 | login = new GuiHackerLogin();
164 | contentPane.add(login, BorderLayout.CENTER);
165 |
166 | projects = new GuiHackerProjectsView();
167 | chooser = new GuiHackerFileChooser();
168 |
169 | setLocationRelativeTo(null);
170 | }
171 |
172 | public boolean initAndLogin(String username, char[] password) {
173 | setLoadingState(true);
174 | GuiHackerBackend.reset();
175 | try {
176 | GuiHackerBackend.init();
177 | GuiHackerBackend.login(username, password);
178 | return true;
179 | } catch (Exception e) {
180 | e.printStackTrace();
181 | setLoadingState(false);
182 | showExceptionDialog(e);
183 | } finally {
184 | setLoadingState(false);
185 | }
186 | return false;
187 | }
188 |
189 | public void setLoadingState(final boolean state) {
190 | try {
191 | Runnable doRun = new Runnable() {
192 | @Override
193 | public void run() {
194 | loading.setLocationRelativeTo(ThumbnailGuiHacker.this);
195 | loading.setVisible(state);
196 | }
197 | };
198 | if (SwingUtilities.isEventDispatchThread())
199 | doRun.run();
200 | else
201 | SwingUtilities.invokeAndWait(doRun);
202 | } catch (InvocationTargetException e) {
203 | e.printStackTrace();
204 | } catch (InterruptedException e) {
205 | e.printStackTrace();
206 | }
207 | }
208 |
209 | public void showExceptionDialog(Throwable e) {
210 | String message = null;
211 | int x = JOptionPane.showOptionDialog(this, "An error has occurred: "
212 | + (((message = e.getMessage()) == null) ? "Unknown" : message),
213 | "Error", JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE,
214 | null, new Object[] { "OK", "View Details", "Exit" }, 0);
215 | if (x == 1) {
216 | StringWriter stack = new StringWriter();
217 | e.printStackTrace(new PrintWriter(stack));
218 | JOptionPane.showMessageDialog(this, stack.toString(),
219 | "Error Details", JOptionPane.ERROR_MESSAGE);
220 | showExceptionDialog(e);
221 | }
222 | if (x == 2)
223 | System.exit(-1);
224 | }
225 |
226 | public void success() {
227 | setLoadingState(false);
228 | int x = JOptionPane
229 | .showOptionDialog(
230 | this,
231 | "Success! Thumbnail has been changed.\nRemember that opening "
232 | + "the editor on your project\nwill change the thumbnail again.",
233 | "Swag", JOptionPane.YES_NO_OPTION,
234 | JOptionPane.INFORMATION_MESSAGE, null,
235 | new Object[] { "Got it; Exit this app",
236 | "Change another one!" }, 0);
237 | if (x == 0)
238 | System.exit(0);
239 | else
240 | goToProjects();
241 | }
242 |
243 | public boolean warnAnnoyingGif() {
244 | return JOptionPane
245 | .showOptionDialog(
246 | this,
247 | "Warning: That GIF has a framerate that's too high, and may be annoying.",
248 | "Warning", JOptionPane.YES_NO_OPTION,
249 | JOptionPane.WARNING_MESSAGE, null, new Object[] {
250 | "I'll choose another GIF",
251 | "I understand, proceed" }, 0) == 1;
252 | }
253 |
254 | public void goToProjects() {
255 | SwingUtilities.invokeLater(new Runnable() {
256 | @Override
257 | public void run() {
258 | setTitle("Thumbnail Hacker - Select a project:");
259 | contentPane.remove(chooser);
260 | contentPane.remove(login);
261 | contentPane.add(projects, BorderLayout.CENTER);
262 | ThumbnailGuiHacker.this.revalidate();
263 | ThumbnailGuiHacker.this.repaint();
264 | }
265 | });
266 | setLoadingState(true);
267 | try {
268 | JSONArray projects = GuiHackerBackend.getProjects();
269 | for (int i = 0; i < projects.length(); i++) {
270 | JSONObject obj = projects.getJSONObject(i);
271 | int id = obj.getInt("pk");
272 | String title = obj.getJSONObject("fields").getString("title");
273 | this.projects.listModel.addElement(new Project(title, id));
274 | }
275 | this.projects.list.setEnabled(true);
276 | this.projects.list.setModel(this.projects.listModel);
277 | this.projects.btnChangeIt.setEnabled(true);
278 | this.projects.revalidate();
279 | } catch (Exception e) {
280 | e.printStackTrace();
281 | setLoadingState(false);
282 | showExceptionDialog(e);
283 | } finally {
284 | setLoadingState(false);
285 | }
286 | }
287 |
288 | public void goToFileChooser(Project selected) {
289 | setTitle("Thumbnail Hacker - Choose a new thumbnail:");
290 | selectedProject = selected;
291 | SwingUtilities.invokeLater(new Runnable() {
292 | @Override
293 | public void run() {
294 | contentPane.remove(projects);
295 | contentPane.add(chooser, BorderLayout.CENTER);
296 | ThumbnailGuiHacker.this.revalidate();
297 | }
298 | });
299 | }
300 |
301 | public void executeHack(final File selected, final String mimeType) {
302 | setLoadingState(true);
303 | new Thread(new Runnable() {
304 | @Override
305 | public void run() {
306 | try {
307 | loadingP.setIndeterminate(false);
308 | GuiHackerBackend.hackThumbnail(selectedProject.id,
309 | selected, mimeType);
310 | loadingP.setIndeterminate(true);
311 | loading.setVisible(false);
312 | success();
313 | } catch (Exception e) {
314 | e.printStackTrace();
315 | setLoadingState(false);
316 | showExceptionDialog(e);
317 | } finally {
318 | setLoadingState(false);
319 | }
320 | }
321 | }).start();
322 | }
323 |
324 | public void setProgress(final int progress) {
325 | try {
326 | SwingUtilities.invokeAndWait(new Runnable() {
327 | @Override
328 | public void run() {
329 | loadingP.setValue(progress);
330 | }
331 | });
332 | } catch (InvocationTargetException e) {
333 | e.printStackTrace();
334 | } catch (InterruptedException e) {
335 | e.printStackTrace();
336 | }
337 | }
338 | }
339 |
--------------------------------------------------------------------------------
/src/apu/scratch/hax/GuiHackerFileChooser.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | package apu.scratch.hax;
5 |
6 | import java.awt.BorderLayout;
7 | import java.awt.Component;
8 | import java.awt.Container;
9 | import java.awt.event.ActionEvent;
10 | import java.awt.event.ActionListener;
11 | import java.awt.image.BufferedImage;
12 | import java.awt.image.ColorModel;
13 | import java.awt.image.RenderedImage;
14 | import java.awt.image.WritableRaster;
15 | import java.io.File;
16 | import java.io.FileOutputStream;
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 | import java.nio.file.Files;
20 | import java.nio.file.Paths;
21 | import java.util.ArrayList;
22 | import java.util.Hashtable;
23 | import java.util.Iterator;
24 | import java.util.List;
25 |
26 | import javax.imageio.IIOImage;
27 | import javax.imageio.ImageIO;
28 | import javax.imageio.ImageReader;
29 | import javax.imageio.ImageWriter;
30 | import javax.imageio.metadata.IIOMetadataNode;
31 | import javax.imageio.stream.FileImageInputStream;
32 | import javax.imageio.stream.FileImageOutputStream;
33 | import javax.imageio.stream.ImageInputStream;
34 | import javax.imageio.stream.ImageOutputStream;
35 | import javax.swing.JButton;
36 | import javax.swing.JFileChooser;
37 | import javax.swing.JPanel;
38 | import javax.swing.SwingUtilities;
39 | import javax.swing.filechooser.FileFilter;
40 |
41 | import apu.scratch.hax.GuiHackerFileChooser.GuiHackerFileFilter.FileFilterType;
42 |
43 | /**
44 | * File chooser
45 | *
46 | * @author "MegaApuTurkUltra"
47 | */
48 | public class GuiHackerFileChooser extends JPanel {
49 | private static final long serialVersionUID = 8323299434700855672L;
50 | JFileChooser fileChooser;
51 |
52 | static class GuiHackerFileFilter extends FileFilter {
53 | static enum FileFilterType {
54 | GIF(new String[] { "gif" }, "image/gif", "GIF Images"), PNG(
55 | new String[] { "png" }, "image/png", "PNG Images"), JPEG(
56 | new String[] { "jpeg", "jpg" }, "image/jpeg", "JPEG Images"), OTHER(
57 | null, "application/octet-stream", "Other files");
58 | String mime;
59 | String[] ext;
60 | String desc;
61 |
62 | private FileFilterType(String[] e, String m, String d) {
63 | ext = e;
64 | mime = m;
65 | desc = d;
66 | }
67 |
68 | public String getDesc() {
69 | return desc;
70 | }
71 |
72 | public String getMimeType() {
73 | return mime;
74 | }
75 |
76 | public boolean isFileAccepted(File f) {
77 | if (ext == null)
78 | return !f.isDirectory() && f.exists();
79 | for (String s : ext) {
80 | if (f.getName().endsWith(s) && f.exists())
81 | return true;
82 | }
83 | return false;
84 | }
85 | }
86 |
87 | FileFilterType type;
88 |
89 | public GuiHackerFileFilter(FileFilterType t) {
90 | type = t;
91 | }
92 |
93 | @Override
94 | public boolean accept(File f) {
95 | return type.isFileAccepted(f) || f.isDirectory();
96 | }
97 |
98 | @Override
99 | public String getDescription() {
100 | return type.getDesc();
101 | }
102 | }
103 |
104 | /**
105 | * Create the panel.
106 | */
107 | public GuiHackerFileChooser() {
108 | fileChooser = new JFileChooser();
109 | fileChooser.setMultiSelectionEnabled(false);
110 | fileChooser.setFileHidingEnabled(false);
111 | fileChooser.setAcceptAllFileFilterUsed(false);
112 | fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
113 | for (FileFilterType type : FileFilterType.values()) {
114 | fileChooser.addChoosableFileFilter(new GuiHackerFileFilter(type));
115 | }
116 | fileChooser.setApproveButtonText("Hack it!");
117 | setLayout(new BorderLayout(0, 0));
118 | add(fileChooser);
119 | SwingUtilities.invokeLater(new Runnable() {
120 | @Override
121 | public void run() {
122 | try {
123 | removeCancelButton(fileChooser);
124 | } catch (Exception e) {
125 | e.printStackTrace();
126 | // continue and ignore. The cancel button doesn't do
127 | // anything anyway
128 | }
129 | }
130 | });
131 |
132 | fileChooser.addActionListener(new ActionListener() {
133 | @Override
134 | public void actionPerformed(ActionEvent e) {
135 | SwingUtilities.invokeLater(new Runnable() {
136 | @Override
137 | public void run() {
138 | FileFilterType type = ((GuiHackerFileFilter) fileChooser
139 | .getFileFilter()).type;
140 | File f = fileChooser.getSelectedFile();
141 | if (f == null || f.isDirectory() || !f.exists())
142 | return;
143 | if (type == FileFilterType.GIF) {
144 | if (!checkGifFrameRate(f))
145 | return;
146 |
147 | // try {
148 | // f = watermark(f);
149 | // } catch (Exception e) {
150 | // e.printStackTrace();
151 | // ThumbnailGuiHacker.INSTANCE.showExceptionDialog(e);
152 | // }
153 | }
154 |
155 | ThumbnailGuiHacker.INSTANCE.executeHack(f, type.mime);
156 | }
157 | });
158 | }
159 | });
160 | }
161 |
162 | public static void removeCancelButton(Container c) {
163 | for (int i = 0; i < c.getComponentCount(); i++) {
164 | Component comp = c.getComponent(i);
165 |
166 | if (comp instanceof JButton) {
167 | JButton b = (JButton) comp;
168 |
169 | if (b != null && b.getText() != null
170 | && b.getText().equals("Cancel")) {
171 | c.remove(b);
172 | }
173 |
174 | } else if (comp instanceof Container) {
175 | removeCancelButton((Container) comp);
176 | }
177 | }
178 | }
179 |
180 | public boolean checkGifFrameRate(File f) {
181 | try {
182 | FileImageInputStream in = new FileImageInputStream(f);
183 | List delays = getGifDelays(in);
184 | in.close();
185 | boolean shortDelay = false;
186 | for (Integer x : delays) {
187 | if (x.intValue() < 50) {
188 | shortDelay = true;
189 | break;
190 | }
191 | }
192 | if (shortDelay) {
193 | return ThumbnailGuiHacker.INSTANCE.warnAnnoyingGif();
194 | }
195 | } catch (Exception e) {
196 | e.printStackTrace();
197 | ThumbnailGuiHacker.INSTANCE.showExceptionDialog(e);
198 | }
199 | return true;
200 | }
201 |
202 | public static void main(String[] args) throws Exception {
203 | File test = new File("sample path");
204 | File wm = watermark(test);
205 | Files.copy(Paths.get(wm.toURI()), new FileOutputStream("test.gif"));
206 | }
207 |
208 | /*
209 | * java, y u make dis so complicated?
210 | */
211 | public static File watermark(File gif) throws Exception {
212 | ImageInputStream iin = new FileImageInputStream(gif);
213 | File tmp = File.createTempFile("thumb_", ".gif");
214 | tmp.deleteOnExit();
215 | ImageOutputStream iout = new FileImageOutputStream(tmp);
216 |
217 | InputStream wmarkin = ThumbnailGuiHacker.class
218 | .getResourceAsStream("/apu/scratch/hax/watermark.png");
219 | BufferedImage wmark = ImageIO.read(wmarkin);
220 | wmarkin.close();
221 |
222 | Iterator readers = ImageIO.getImageReaders(iin);
223 |
224 | ImageReader reader = null;
225 | while (readers.hasNext()) {
226 | reader = readers.next();
227 |
228 | String metaFormat = reader.getOriginatingProvider()
229 | .getNativeImageMetadataFormatName();
230 | if ("gif".equalsIgnoreCase(reader.getFormatName())
231 | && !"javax_imageio_gif_image_1.0".equals(metaFormat)) {
232 | continue;
233 | } else {
234 | break;
235 | }
236 | }
237 | if (reader == null) {
238 | iout.close();
239 | throw new IOException("Can not read image format!");
240 | }
241 |
242 | ImageWriter writer = ImageIO.getImageWriter(reader);
243 | writer.setOutput(iout);
244 |
245 | boolean isGif = reader.getFormatName().equalsIgnoreCase("gif");
246 | reader.setInput(iin, false, !isGif);
247 | writer.prepareWriteSequence(reader.getStreamMetadata());
248 | for (int index = 0;; index++) {
249 | try {
250 | // read a frame and its metadata
251 | IIOImage frame = reader.readAll(index, null);
252 | RenderedImage img = frame.getRenderedImage();
253 | if (img == null)
254 | throw new IllegalStateException("img == null");
255 | BufferedImage newImg = convertRenderedImage(img);
256 | newImg.getGraphics().drawImage(wmark, 0, 0, newImg.getWidth(),
257 | newImg.getHeight(), 0, 0, wmark.getWidth(),
258 | wmark.getHeight(), null);
259 | frame.setRenderedImage(newImg);
260 | writer.writeToSequence(frame, null);
261 | } catch (IndexOutOfBoundsException e) {
262 | break;
263 | }
264 | }
265 |
266 | // clean up
267 | reader.dispose();
268 | iout.close();
269 | writer.dispose();
270 | Files.copy(Paths.get(tmp.toURI()), new FileOutputStream("test.gif"));
271 | return tmp;
272 | }
273 |
274 | public static BufferedImage convertRenderedImage(RenderedImage img) {
275 | if (img instanceof BufferedImage) {
276 | return (BufferedImage) img;
277 | }
278 | ColorModel cm = img.getColorModel();
279 | int width = img.getWidth();
280 | int height = img.getHeight();
281 | WritableRaster raster = cm
282 | .createCompatibleWritableRaster(width, height);
283 | boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
284 | Hashtable properties = new Hashtable<>();
285 | String[] keys = img.getPropertyNames();
286 | if (keys != null) {
287 | for (int i = 0; i < keys.length; i++) {
288 | properties.put(keys[i], img.getProperty(keys[i]));
289 | }
290 | }
291 | BufferedImage result = new BufferedImage(cm, raster,
292 | isAlphaPremultiplied, properties);
293 | img.copyData(raster);
294 | return result;
295 | }
296 |
297 | private List getGifDelays(ImageInputStream imageStream)
298 | throws IOException {
299 |
300 | // obtain an appropriate src reader
301 | Iterator readers = ImageIO.getImageReaders(imageStream);
302 |
303 | ImageReader reader = null;
304 | while (readers.hasNext()) {
305 | reader = readers.next();
306 |
307 | String metaFormat = reader.getOriginatingProvider()
308 | .getNativeImageMetadataFormatName();
309 | if ("gif".equalsIgnoreCase(reader.getFormatName())
310 | && !"javax_imageio_gif_image_1.0".equals(metaFormat)) {
311 | continue;
312 | } else {
313 | break;
314 | }
315 | }
316 | if (reader == null) {
317 | throw new IOException("Can not read image format!");
318 | }
319 | boolean isGif = reader.getFormatName().equalsIgnoreCase("gif");
320 | reader.setInput(imageStream, false, !isGif);
321 | List delays = new ArrayList();
322 | boolean unkownMetaFormat = false;
323 | for (int index = 0;; index++) {
324 | try {
325 | // read a frame and its metadata
326 | IIOImage frame = reader.readAll(index, null);
327 |
328 | if (unkownMetaFormat)
329 | continue;
330 |
331 | // obtain src metadata
332 | javax.imageio.metadata.IIOMetadata meta = frame.getMetadata();
333 |
334 | IIOMetadataNode imgRootNode = null;
335 | try {
336 | imgRootNode = (IIOMetadataNode) meta
337 | .getAsTree("javax_imageio_gif_image_1.0");
338 | } catch (IllegalArgumentException e) {
339 | // unkown metadata format, can't do anyting about this
340 | unkownMetaFormat = true;
341 | continue;
342 | }
343 |
344 | IIOMetadataNode gce = (IIOMetadataNode) imgRootNode
345 | .getElementsByTagName("GraphicControlExtension")
346 | .item(0);
347 |
348 | delays.add(Integer.parseInt(gce.getAttribute("delayTime")));
349 | } catch (IndexOutOfBoundsException e) {
350 | break;
351 | }
352 | }
353 |
354 | // clean up
355 | reader.dispose();
356 | return delays;
357 | }
358 | }
359 |
--------------------------------------------------------------------------------