aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2015-10-24 19:35:41 +0300
committerGravatar Vitaly Takmazov2015-10-24 19:35:41 +0300
commitaf80956ec669cf48ea6423959b5a4d8f16385d6f (patch)
tree3589c77814185cd8bf43408f46793aaf8917c27a /src/main
parent7a96f3f799e2e2358be246b3e9f0aa412ef28a2d (diff)
moving to Gradle
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/com/juick/Message.java171
-rw-r--r--src/main/java/com/juick/Tag.java35
-rw-r--r--src/main/java/com/juick/User.java53
-rw-r--r--src/main/java/com/juick/http/www/Discover.java120
-rw-r--r--src/main/java/com/juick/http/www/Errors.java44
-rw-r--r--src/main/java/com/juick/http/www/FacebookLogin.java187
-rw-r--r--src/main/java/com/juick/http/www/Help.java94
-rw-r--r--src/main/java/com/juick/http/www/Home.java163
-rw-r--r--src/main/java/com/juick/http/www/Login.java259
-rw-r--r--src/main/java/com/juick/http/www/Main.java322
-rw-r--r--src/main/java/com/juick/http/www/NewMessage.java420
-rw-r--r--src/main/java/com/juick/http/www/PM.java231
-rw-r--r--src/main/java/com/juick/http/www/PageTemplates.java584
-rw-r--r--src/main/java/com/juick/http/www/RSS.java113
-rw-r--r--src/main/java/com/juick/http/www/Settings.java104
-rw-r--r--src/main/java/com/juick/http/www/SignUp.java331
-rw-r--r--src/main/java/com/juick/http/www/User.java413
-rw-r--r--src/main/java/com/juick/http/www/UserThread.java370
-rw-r--r--src/main/java/com/juick/http/www/Utils.java244
-rw-r--r--src/main/java/com/juick/http/www/VKontakteLogin.java155
-rw-r--r--src/main/java/ru/sape/Sape.java25
-rw-r--r--src/main/java/ru/sape/SapeConnection.java107
-rw-r--r--src/main/java/ru/sape/SapePageLinks.java95
-rw-r--r--src/main/java/ru/sape/SerializedPhpParser.java221
-rw-r--r--src/main/webapp/WEB-INF/web.xml16
-rw-r--r--src/main/webapp/favicon.pngbin0 -> 244 bytes
-rw-r--r--src/main/webapp/logo.pngbin0 -> 1184 bytes
-rw-r--r--src/main/webapp/scripts.js684
-rw-r--r--src/main/webapp/style.css249
29 files changed, 5810 insertions, 0 deletions
diff --git a/src/main/java/com/juick/Message.java b/src/main/java/com/juick/Message.java
new file mode 100644
index 00000000..65c9c015
--- /dev/null
+++ b/src/main/java/com/juick/Message.java
@@ -0,0 +1,171 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2011, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class Message {
+
+ public int MID = 0;
+ public int RID = 0;
+ public int ReplyTo = 0;
+ public String Text = null;
+ public User User = null;
+ public ArrayList<String> Tags = new ArrayList<String>();
+ public Date Timestamp = null;
+ public String TimestampString = null;
+ public int TimeAgo = 0;
+ public int Privacy = 1;
+ public boolean FriendsOnly = false;
+ public boolean ReadOnly = false;
+ public boolean Hidden = false;
+ public boolean VisitorCanComment = true;
+ public int Replies = 0;
+ public String RepliesBy = null;
+ public String AttachmentType = null;
+ public String Photo = null;
+ public String Video = null;
+ public int Likes = 0;
+ public boolean UserLike = false;
+ public ArrayList<Message> childs = new ArrayList<Message>();
+
+ public Message() {
+ }
+
+ public Message(Message msg) {
+ MID = msg.MID;
+ RID = msg.RID;
+ ReplyTo = msg.ReplyTo;
+ Text = msg.Text;
+ User = msg.User;
+ Tags = msg.Tags;
+ Timestamp = msg.Timestamp;
+ TimestampString = msg.TimestampString;
+ TimeAgo = msg.TimeAgo;
+ Privacy = msg.Privacy;
+ FriendsOnly = msg.FriendsOnly;
+ ReadOnly = msg.ReadOnly;
+ Hidden = msg.Hidden;
+ Replies = msg.Replies;
+ AttachmentType = msg.AttachmentType;
+ Photo = msg.Photo;
+ Video = msg.Video;
+ Likes = msg.Likes;
+ UserLike = msg.UserLike;
+ childs = msg.childs;
+ }
+
+ public void parseTags(String strTags) {
+ Tags.addAll(Arrays.asList(strTags.split(" ")));
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Message)) {
+ return false;
+ }
+ Message jmsg = (Message) obj;
+ return (this.MID == jmsg.MID && this.RID == jmsg.RID);
+ }
+
+ public int compareTo(Object obj) throws ClassCastException {
+ if (!(obj instanceof Message)) {
+ throw new ClassCastException();
+ }
+ Message jmsg = (Message) obj;
+
+ if (this.MID != jmsg.MID) {
+ if (this.MID > jmsg.MID) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ if (this.RID != jmsg.RID) {
+ if (this.RID < jmsg.RID) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+
+ public int getChildsCount() {
+ int cnt = childs.size();
+ for (int i = 0; i < childs.size(); i++) {
+ cnt += childs.get(i).getChildsCount();
+ }
+ return cnt;
+ }
+
+ public void cleanupChilds() {
+ if (!childs.isEmpty()) {
+ for (int i = 0; i < childs.size(); i++) {
+ childs.get(i).cleanupChilds();
+ }
+ childs.clear();
+ }
+ }
+
+ public String getAttachmentURL() {
+ if (AttachmentType != null) {
+ String url = "http://i.juick.com/";
+ url += AttachmentType.equals("mp4") ? "video" : "photos-1024";
+ url += "/" + MID;
+ if (RID > 0) {
+ url += "-" + RID;
+ }
+ url += "." + AttachmentType;
+ return url;
+ } else {
+ return null;
+ }
+ }
+
+ public String getTagsString() {
+ String ret = "";
+ for (int i = 0; i < Tags.size(); i++) {
+ ret += " *" + Tags.get(i);
+ }
+ if (FriendsOnly) {
+ ret += " *friends";
+ }
+ if (Privacy == -2) {
+ ret += " *private";
+ }
+ if (Privacy == -1) {
+ ret += " *friends";
+ }
+ if (Privacy == 2) {
+ ret += " *public";
+ }
+ if (ReadOnly) {
+ ret += " *readonly";
+ }
+ return ret;
+ }
+}
diff --git a/src/main/java/com/juick/Tag.java b/src/main/java/com/juick/Tag.java
new file mode 100644
index 00000000..3cee3358
--- /dev/null
+++ b/src/main/java/com/juick/Tag.java
@@ -0,0 +1,35 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2011, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class Tag implements Comparable<Tag> {
+
+ public String Name = null;
+ public int TID = 0;
+ public int SynonymID = 0;
+ public int UsageCnt = 0;
+
+ @Override
+ public int compareTo(Tag o) {
+ return this.Name.compareTo(o.Name);
+ }
+}
diff --git a/src/main/java/com/juick/User.java b/src/main/java/com/juick/User.java
new file mode 100644
index 00000000..e3fc0ae7
--- /dev/null
+++ b/src/main/java/com/juick/User.java
@@ -0,0 +1,53 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2011, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class User {
+
+ public int UID = 0;
+ public String UName = null;
+ public Object Avatar = null;
+ public String FullName = null;
+ public String JID = null;
+ public int MessagesCount = 0;
+ public String AuthHash = null;
+ public boolean Banned = false;
+
+ public User() {
+ }
+
+ public User(User u) {
+ UID = u.UID;
+ UName = u.UName;
+ Avatar = u.Avatar;
+ FullName = u.FullName;
+ JID = u.JID;
+ MessagesCount = u.MessagesCount;
+ AuthHash = u.AuthHash;
+ Banned = u.Banned;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return (obj instanceof User && ((User) obj).UID == this.UID);
+ }
+}
diff --git a/src/main/java/com/juick/http/www/Discover.java b/src/main/java/com/juick/http/www/Discover.java
new file mode 100644
index 00000000..201139c4
--- /dev/null
+++ b/src/main/java/com/juick/http/www/Discover.java
@@ -0,0 +1,120 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2011, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick.http.www;
+
+import com.juick.server.AdsQueries;
+import com.juick.server.MessagesQueries;
+import com.juick.server.TagQueries;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.sql.Connection;
+import java.util.ArrayList;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class Discover {
+
+ protected void doGet(Connection sql, Connection sqlSearch, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+
+ String paramTagStr = URLDecoder.decode(request.getRequestURI().substring(5), "UTF-8");
+ com.juick.Tag paramTag = TagQueries.getTag(sql, paramTagStr, false);
+ if (paramTag == null) {
+ Errors.doGet404(sql, request, response);
+ return;
+ } else if (paramTag.SynonymID > 0 && paramTag.TID != paramTag.SynonymID) {
+ com.juick.Tag synTag = TagQueries.getTag(sql, paramTag.SynonymID);
+ String url = "/tag/" + URLEncoder.encode(synTag.Name, "UTF-8");
+ if (request.getQueryString() != null) {
+ url += "?" + request.getQueryString();
+ }
+ Utils.sendPermanentRedirect(response, url);
+ return;
+ } else if (!paramTag.Name.equals(paramTagStr)) {
+ String url = "/tag/" + URLEncoder.encode(paramTag.Name, "UTF-8");
+ if (request.getQueryString() != null) {
+ url += "?" + request.getQueryString();
+ }
+ Utils.sendPermanentRedirect(response, url);
+ return;
+ }
+
+ int paramBefore = 0;
+ String paramBeforeStr = request.getParameter("before");
+ if (paramBeforeStr != null) {
+ try {
+ paramBefore = Integer.parseInt(paramBeforeStr);
+ } catch (NumberFormatException e) {
+ }
+ }
+
+ int visitor_uid = visitor != null ? visitor.UID : 0;
+
+ String title = "*" + Utils.encodeHTML(paramTag.Name);
+ ArrayList<Integer> mids = MessagesQueries.getTag(sql, paramTag.TID, visitor_uid, paramBefore, (visitor == null) ? 40 : 20);
+
+ response.setContentType("text/html; charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ try {
+ String head = "";
+ if (TagQueries.getTagNoIndex(sql, paramTag.TID)) {
+ head = "<meta name=\"robots\" content=\"noindex,nofollow\"/>";
+ } else if (paramBefore > 0 || mids.size() < 5) {
+ head = "<meta name=\"robots\" content=\"noindex\"/>";
+ }
+ PageTemplates.pageHead(out, title, head);
+ PageTemplates.pageNavigation(out, visitor, null);
+ PageTemplates.pageHomeColumn(out, sql, visitor);
+
+ out.println("<section id=\"content\">");
+
+ if (mids.size() > 0) {
+ int vuid = visitor != null ? visitor.UID : 0;
+ int ad_mid = AdsQueries.getAdMID(sql, vuid);
+ if (ad_mid > 0 && mids.indexOf(ad_mid) == -1) {
+ mids.add(0, ad_mid);
+ AdsQueries.logAdMID(sql, vuid, ad_mid);
+ } else {
+ ad_mid = 0;
+ }
+
+ PageTemplates.printMessages(out, sql, null, mids, visitor, visitor == null ? 2 : 3, ad_mid);
+ }
+
+ if (mids.size() >= 20) {
+ String nextpage = "/tag/" + URLEncoder.encode(paramTag.Name, "UTF-8") + "?before=" + mids.get(mids.size() - 1);
+ out.println("<p class=\"page\"><a href=\"" + nextpage + "\" rel=\"prev\">Читать дальше →</a></p>");
+ }
+
+ out.println("</section>");
+
+ PageTemplates.pageFooter(request, out, visitor, true);
+
+ PageTemplates.pageEnd(out);
+ } finally {
+ out.close();
+ }
+ }
+}
diff --git a/src/main/java/com/juick/http/www/Errors.java b/src/main/java/com/juick/http/www/Errors.java
new file mode 100644
index 00000000..f65b6201
--- /dev/null
+++ b/src/main/java/com/juick/http/www/Errors.java
@@ -0,0 +1,44 @@
+package com.juick.http.www;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ *
+ * @author ugnich
+ */
+public class Errors {
+
+ public static String tagsHTML = null;
+
+ public static void doGet404(Connection sql, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+
+ if (tagsHTML == null) {
+ tagsHTML = PageTemplates.getPopularTags(sql, 80);
+ }
+
+ response.setStatus(404);
+ response.setContentType("text/html; charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ try {
+ PageTemplates.pageHead(out, "404 Страница не найдена", null);
+ PageTemplates.pageNavigation(out, visitor, null);
+ PageTemplates.pageHomeColumn(out, sql, visitor);
+
+ out.println("<section id=\"content\">");
+ out.println("<h1>Страница не найдена</h1>");
+ out.println("<p>Сожалеем, но страницу с этим адресом удалил её автор, либо её никогда не существовало.</p>");
+ out.println("</section>");
+
+ PageTemplates.pageFooter(request, out, visitor, false);
+ PageTemplates.pageEnd(out);
+ } finally {
+ out.close();
+ }
+ }
+}
diff --git a/src/main/java/com/juick/http/www/FacebookLogin.java b/src/main/java/com/juick/http/www/FacebookLogin.java
new file mode 100644
index 00000000..9658c0d6
--- /dev/null
+++ b/src/main/java/com/juick/http/www/FacebookLogin.java
@@ -0,0 +1,187 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2013, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick.http.www;
+
+import com.juick.server.UserQueries;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.json.JSONObject;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class FacebookLogin {
+
+ private static final String FACEBOOK_APPID = "130568668304";
+ private static final String FACEBOOK_SECRET = "95813bfb6ab8f473410c50d4f971649e";
+ private static final String FACEBOOK_REDIRECT = "http://juick.com/_fblogin";
+
+ protected void doGet(Connection sql, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ String fbstate;
+
+ String code = request.getParameter("code");
+ if (code == null || code.equals("")) {
+ fbstate = UUID.randomUUID().toString();
+
+ Cookie c = new Cookie("fbstate", fbstate);
+ response.addCookie(c);
+
+ response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
+ response.setHeader("Location", "https://www.facebook.com/dialog/oauth?scope=publish_stream&client_id=" + FACEBOOK_APPID + "&redirect_uri=" + URLEncoder.encode(FACEBOOK_REDIRECT, "utf-8") + "&state=" + fbstate);
+ return;
+ }
+
+ fbstate = Utils.getCookie(request, "fbstate");
+ if (fbstate == null || fbstate.isEmpty() || !fbstate.equals(request.getParameter("state"))) {
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ } else {
+ Cookie c = new Cookie("fbstate", "-");
+ c.setMaxAge(0);
+ response.addCookie(c);
+ }
+
+ String token = Utils.fetchURL("https://graph.facebook.com/oauth/access_token?client_id=" + FACEBOOK_APPID + "&redirect_uri=" + URLEncoder.encode(FACEBOOK_REDIRECT, "utf-8") + "&client_secret=" + FACEBOOK_SECRET + "&code=" + URLEncoder.encode(code, "utf-8"));
+ if (token == null || token.isEmpty() || !token.startsWith("access_token=")) {
+ System.err.println("FACEBOOK TOKEN ERROR: " + token);
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ token = token.substring(13); // access_token=...
+ int tokenamp = token.indexOf('&'); // &expires=
+ if (tokenamp > 0) {
+ token = token.substring(0, tokenamp);
+ }
+
+ String graph = Utils.fetchURL("https://graph.facebook.com/me?access_token=" + token);
+ if (graph == null || graph.isEmpty()) {
+ System.err.println("FACEBOOK GRAPH ERROR");
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ try {
+ JSONObject json = new JSONObject(graph);
+ String fbIDStr = json.getString("id");
+ String fbName = json.getString("name");
+ String fbLink = json.getString("link");
+ boolean fbVerified = json.getBoolean("verified");
+
+ long fbID = 0;
+ if (fbIDStr != null && !fbIDStr.isEmpty()) {
+ fbID = Long.parseLong(fbIDStr);
+ }
+
+ if (fbID == 0 || fbName == null || fbLink == null || fbName.isEmpty() || fbLink.isEmpty()) {
+ throw new Exception();
+ }
+
+ int uid = getUIDbyFBID(sql, fbID);
+ if (uid > 0) {
+ if (!updateDB(sql, fbID, token, fbName, fbLink)) {
+ throw new Exception();
+ }
+ Cookie c = new Cookie("hash", UserQueries.getHashByUID(sql, uid));
+ c.setMaxAge(50 * 24 * 60 * 60);
+ response.addCookie(c);
+ response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
+ response.setHeader("Location", "/");
+ } else if (fbVerified) {
+ String loginhash = UUID.randomUUID().toString();
+ if (!insertDB(sql, fbID, loginhash, token, fbName, fbLink)) {
+ throw new Exception();
+ }
+ response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
+ response.setHeader("Location", "/signup?type=fb&hash=" + loginhash);
+ } else {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ }
+
+ private int getUIDbyFBID(Connection sql, long fbID) {
+ int uid = 0;
+ PreparedStatement stmt = null;
+ ResultSet rs = null;
+ try {
+ stmt = sql.prepareStatement("SELECT user_id FROM facebook WHERE fb_id=? AND user_id IS NOT NULL");
+ stmt.setLong(1, fbID);
+ rs = stmt.executeQuery();
+ if (rs.first()) {
+ uid = rs.getInt(1);
+ }
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(rs, stmt);
+ }
+ return uid;
+ }
+
+ private boolean insertDB(Connection sql, long fbID, String loginhash, String token, String fbName, String fbLink) {
+ boolean ret = false;
+ PreparedStatement stmt = null;
+ try {
+ stmt = sql.prepareStatement("INSERT INTO facebook(fb_id,loginhash,access_token,fb_name,fb_link) VALUES (?,?,?,?,?)");
+ stmt.setLong(1, fbID);
+ stmt.setString(2, loginhash);
+ stmt.setString(3, token);
+ stmt.setString(4, fbName);
+ stmt.setString(5, fbLink);
+ stmt.executeUpdate();
+ ret = true;
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(null, stmt);
+ }
+ return ret;
+ }
+
+ private boolean updateDB(Connection sql, long fbID, String token, String fbName, String fbLink) {
+ boolean ret = false;
+ PreparedStatement stmt = null;
+ try {
+ stmt = sql.prepareStatement("UPDATE facebook SET access_token=?,fb_name=?,fb_link=? WHERE fb_id=?");
+ stmt.setString(1, token);
+ stmt.setString(2, fbName);
+ stmt.setString(3, fbLink);
+ stmt.setLong(4, fbID);
+ stmt.executeUpdate();
+ ret = true;
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(null, stmt);
+ }
+ return ret;
+ }
+}
diff --git a/src/main/java/com/juick/http/www/Help.java b/src/main/java/com/juick/http/www/Help.java
new file mode 100644
index 00000000..eed62efa
--- /dev/null
+++ b/src/main/java/com/juick/http/www/Help.java
@@ -0,0 +1,94 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2011, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick.http.www;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class Help {
+
+ protected void doRedirectToHelpIndex(Connection sql, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ Utils.sendTemporaryRedirect(response, "/help/ru/");
+ }
+
+ protected void doGetHelp(Connection sql, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+
+ String path[] = request.getRequestURI().split("/");
+ String page;
+ if (path.length < 3 || path.length > 4 || path[2].length() != 2 || !path[2].matches("^[a-z]+$")) {
+ Errors.doGet404(sql, request, response);
+ return;
+ }
+
+ if (path.length == 4) {
+ page = path[3];
+ if (!page.matches("^[a-zA-Z0-9\\-]*$") || page.equals("navigation") || page.equals("index")) {
+ Errors.doGet404(sql, request, response);
+ return;
+ }
+ } else {
+ page = "index";
+ }
+
+ File f = new File("/var/www/juick.com/help/" + path[2] + "/" + page);
+ if (!f.isFile()) {
+ Errors.doGet404(sql, request, response);
+ return;
+ }
+
+ response.setContentType("text/html; charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ try {
+ PageTemplates.pageHead(out, "Помощь", null);
+ PageTemplates.pageNavigation(out, visitor, null);
+
+ out.println("<aside id=\"column\">");
+ printFile(out, new File("/var/www/juick.com/help/" + path[2] + "/navigation"));
+ out.println("</aside>");
+
+ out.println("<section id=\"content\">");
+ printFile(out, f);
+ out.println("</section>");
+
+ PageTemplates.pageFooter(request, out, visitor, false);
+ PageTemplates.pageEnd(out);
+ } finally {
+ out.close();
+ }
+ }
+
+ private void printFile(PrintWriter out, File f) throws IOException {
+ BufferedReader br = new BufferedReader(new FileReader(f));
+ String str;
+ while ((str = br.readLine()) != null) {
+ out.println(str);
+ }
+ }
+}
diff --git a/src/main/java/com/juick/http/www/Home.java b/src/main/java/com/juick/http/www/Home.java
new file mode 100644
index 00000000..52012182
--- /dev/null
+++ b/src/main/java/com/juick/http/www/Home.java
@@ -0,0 +1,163 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2011, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick.http.www;
+
+import com.juick.server.AdsQueries;
+import com.juick.server.MessagesQueries;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.URLEncoder;
+import java.sql.Connection;
+import java.util.ArrayList;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class Home {
+
+ protected void doGet(Connection sql, Connection sqlSearch, HttpServletRequest request, HttpServletResponse response, com.juick.User visitor) throws ServletException, IOException {
+ int paramBefore = 0;
+ String paramBeforeStr = request.getParameter("before");
+ if (paramBeforeStr != null) {
+ try {
+ paramBefore = Integer.parseInt(paramBeforeStr);
+ } catch (NumberFormatException e) {
+ }
+ }
+
+ String paramSearch = request.getParameter("search");
+ if (paramSearch != null && paramSearch.length() > 64) {
+ paramSearch = null;
+ }
+
+ String title;
+ ArrayList<Integer> mids;
+
+ String paramShow = request.getParameter("show");
+ if (paramSearch != null) {
+ title = "Поиск: " + Utils.encodeHTML(paramSearch);
+ mids = MessagesQueries.getSearch(sql, sqlSearch, Utils.encodeSphinx(paramSearch), paramBefore);
+ } else if (paramShow == null) {
+ if (visitor != null) {
+ title = "Популярные";
+ } else {
+ title = "Микроблоги Juick: популярные записи";
+ }
+ mids = MessagesQueries.getPopular(sql, paramBefore);
+ } else if (paramShow.equals("top")) {
+ Utils.sendPermanentRedirect(response, "/");
+ return;
+ } else if (paramShow.equals("my") && visitor != null) {
+ title = "Моя лента";
+ mids = MessagesQueries.getMyFeed(sql, visitor.UID, paramBefore);
+ } else if (paramShow.equals("private") && visitor != null) {
+ title = "Приватные";
+ mids = MessagesQueries.getPrivate(sql, visitor.UID, paramBefore);
+ } else if (paramShow.equals("discuss") && visitor != null) {
+ title = "Обсуждения";
+ mids = MessagesQueries.getDiscussions(sql, visitor.UID, paramBefore);
+ } else if (paramShow.equals("recommended") && visitor != null) {
+ title = "Рекомендации";
+ mids = MessagesQueries.getRecommended(sql, visitor.UID, paramBefore);
+ } else if (paramShow.equals("all")) {
+ title = "Все сообщения";
+ if (visitor != null) {
+ mids = MessagesQueries.getAll(sql, visitor.UID, paramBefore);
+ } else {
+ mids = MessagesQueries.getAll(sql, 0, paramBefore);
+ }
+ } else {
+ Errors.doGet404(sql, request, response);
+ return;
+ }
+
+ response.setContentType("text/html; charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ try {
+ String head = "";
+ if (paramBefore > 0 || paramShow != null) {
+ head = "<meta name=\"robots\" content=\"noindex\"/>";
+ }
+ PageTemplates.pageHead(out, title, head);
+ PageTemplates.pageNavigation(out, visitor, paramSearch);
+ PageTemplates.pageHomeColumn(out, sql, visitor, paramShow == null && paramBefore == 0 && paramSearch == null && visitor == null);
+
+ out.println("<section id=\"content\">");
+
+ if (paramShow == null && paramBefore == 0) {
+ out.println("<!--noindex-->");
+ }
+
+ if (visitor != null) {
+ out.println("<form action=\"/post\" method=\"post\" enctype=\"multipart/form-data\" onsubmit=\"return onsubmitNewMessage()\">");
+ out.println("<section id=\"newmessage\">");
+ out.println(" <textarea name=\"body\" placeholder=\"Новое сообщение...\" onclick=\"$('#newmessage>div').css('display','block');$('#newmessage textarea').css('min-height','70px');\" onkeypress=\"postformListener(this.form,event)\"></textarea>");
+ out.println(" <div>");
+ out.println(" <input type=\"text\" class=\"img\" name=\"img\" placeholder=\"Ссылка на изображение (JPG/PNG, до 10Мб)\"/> или <a href=\"#\" onclick=\"return attachMessagePhoto(this)\">загрузить</a><br/>");
+ out.println(" <input type=\"text\" class=\"tags\" name=\"tags\" placeholder=\"Теги (через пробел)\"/><br/>");
+ out.println(" <input type=\"submit\" class=\"subm\" value=\"Отправить\"/>");
+ out.println(" </div>");
+ out.println("</section>");
+ out.println("</form>");
+ }
+
+ if (mids.size() > 0) {
+ int ad_mid = 0;
+ if (paramShow == null || paramShow.equals("top") || paramShow.equals("all")) {
+ int vuid = visitor != null ? visitor.UID : 0;
+ ad_mid = AdsQueries.getAdMID(sql, vuid);
+ if (ad_mid > 0 && mids.indexOf(ad_mid) == -1) {
+ mids.add(0, ad_mid);
+ AdsQueries.logAdMID(sql, vuid, ad_mid);
+ } else {
+ ad_mid = 0;
+ }
+ }
+
+ PageTemplates.printMessages(out, sql, null, mids, visitor, visitor == null ? 2 : 3, ad_mid);
+ }
+
+ if (mids.size() >= 20) {
+ String nextpage = "?before=" + mids.get(mids.size() - 1);
+ if (paramShow != null) {
+ nextpage += "&amp;show=" + paramShow;
+ }
+ if (paramSearch != null) {
+ nextpage += "&amp;search=" + URLEncoder.encode(paramSearch, "UTF-8");
+ }
+
+ out.println("<p class=\"page\"><a href=\"" + nextpage + "\" rel=\"prev\">Читать дальше →</a></p>");
+ }
+
+ if (paramShow == null && paramBefore == 0) {
+ out.println("<!--/noindex-->");
+ }
+
+ out.println("</section>");
+
+ PageTemplates.pageFooter(request, out, visitor, true);
+ PageTemplates.pageEnd(out);
+ } finally {
+ out.close();
+ }
+ }
+}
diff --git a/src/main/java/com/juick/http/www/Login.java b/src/main/java/com/juick/http/www/Login.java
new file mode 100644
index 00000000..a24eac2b
--- /dev/null
+++ b/src/main/java/com/juick/http/www/Login.java
@@ -0,0 +1,259 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2011, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick.http.www;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class Login {
+
+ protected void doGetLoginForm(Connection sql, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+ if (visitor != null) {
+ Utils.sendTemporaryRedirect(response, "/");
+ return;
+ }
+
+ response.setContentType("text/html; charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ try {
+ out.println("<!DOCTYPE html>");
+ out.println("<html>");
+ out.println("<head>");
+ out.println("<title>Juick</title>");
+ out.println("<script type=\"text/javascript\" src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js\" defer=\"defer\"></script>");
+ out.println("<style>");
+ out.println("* { margin: 0; padding: 0; }");
+ out.println("html { font-family: sans-serif; font-size: 12pt; }");
+ out.println("html { background: #eeeee5; }");
+ out.println("body { margin: 100px auto 0 auto; width: 1000px; }");
+ out.println("a { color: #069; }");
+ out.println("ul { float: left; width: 700px; height: 350px; list-style-type: none; background: url(/tagscloud.png) no-repeat; position: relative; }");
+ out.println("ul a { position: absolute; display: block; text-indent: 100%; white-space: nowrap; overflow: hidden; }");
+
+ out.println("#bottom1 { position: absolute; left: 0px; bottom: 10px; width: 100%; text-align: center; color: #555; }");
+ out.println("#bottom2 { position: absolute; left: 0px; bottom: -50px; width: 100%; padding-bottom: 20px; text-align: center; font-size: small; color: #777; }");
+
+ out.println("#signup,#signin { margin-left: 730px; width: 250px; }");
+ out.println("#signup { padding-top: 25px; }");
+ out.println("#signup>div { width: 100%; margin: 15px 0; }");
+ out.println("#signup>div>a { display: block; width: 100%; height: 32px; line-height: 32px; text-indent: 37px; text-decoration: none; overflow: hidden; }");
+
+ out.println("#facebook a { color: #FFF; background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAADNQTFRFO1edX3ewl6bLnKrOoK3QrrrYvMXe2N7r3OLu3+Tv5urz7O/29vf6+Pn7+vv9/Pz9////ykQjsQAAAEZJREFUOMtjYBgFuAATO68ADxdOaUYuATDAqYBbAL8CFgECCjiBcqz4XMiPz3oQEKCtAgEkwEdIAQchBWyEFDAPkDdHsAIAhZkIwz/VK/UAAAAASUVORK5CYII=\") no-repeat #3A569C; }");
+ out.println("#vk a { color: #FFF; background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAAHJQTFRFbY+zbo+zbpCzb5C0cpO1c5O2dZW3dpa4e5m6gJ29gZ69lq/In7bNo7jPrcDUs8XXvs3dv87dy9fkztnlz9rm0Nrm093o1N7o1+Dq3OTt3ubu4Ofv5Orw7fH27vL28PP38vX49Pb5+vv8+/z9/Pz9////2jSYlQAAAG5JREFUOMvtkEcOgDAMBE3vvXdIyP+/iMMRKfYHmMtcRtE6AD8f1Is8pyKgAs0RGYO2HSWqMQaoBHVRgYsS3AsrtyFlrqgdJlCLb95gxQO6IkZCqL+KCjz0TQU5ejOf2a3aJXPF7BOB2PvMhp8PDzGRFgEe7xvEAAAAAElFTkSuQmCC\") no-repeat #6d8fb3; }");
+ out.println("#xmpp>a { color: #333; background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAAt9QTFRFBj5rCUFoFz5mDEFuDUNqGUJkGUNgAEprEkVtGkRhHURnHEZjAE+BIElmEEx/HEx0PUdTHE5wMEtfO0laJk5sFlN6Nk1cKFBuIlJ1R0pYRExTGF2KMVh1OFhxDGSQT1deNlx6TVhkIGKPUlphKWOFU1tiOmB+Vl1kmlAuNmaDQGpaIG6ba2Q2SGeBN2uUNW+LF3mGRmyLV3BAL3xWgmVJ2VAa2lEb0VYX11UafWlmam9mam5xy1km3lUea29y2VccvF8obHBz21gd4VcXS3ebPoVG1lwl5FkaU3iYYHaH2F0ejHFIx2Iv4V0aP4o+02As218g4l4bPYtFX351KZFaymU43mIrVYoz5mEfT4w0xGsrSo82eXqDw2s+z2k1OZVAT4SoPZU5RZM4NJVYbYc8VoSiWY43WItbuXBK52gYOI20TpM6YYSYfX98T5Q7foB9aYSZSZc8Ro21cYxH7GwdT46e0HFGeopO2HA8V5ZEf4s8528l1HM7UJs5UI+xXoyrWZhGY4ylyHdMwXhXQZS7XpdNU5tH4XJBYJZZcIuhbpJk0HdPU588kohqOZ2pVZS30XpX6XcxSaFrz4FIUqVWWqVCYZm23YBGxYRrZZ2QXpy/5YFC7IA+6oBRb6ZUcKZn44ZSgKJu54hHmJqXbbBNfKh3cq5nYqrMpJyVdrBjx5WCo56diamAjaWdyZeE8JFV8ZJWnqOlaK/Sd7Zh7pVdgbN6x52N8JdfwqCU8ZhgZLbXb7a5grtTmK+UxKKWtqahq6qhd7bHqauop6yvqqyp755pbbzRrK6rwamgib53qq+xra+sobSg86Jza8PJsLKujMVctrGwqLapv7Crr7Wr8ad1srSxj8R9uLOys7WyubSztLazkcZ/sre6tbe0r7m0tri1t7m2vri3uLq3v7m4ubu4nc9mpNBvos2UptJxtNWXtteFuNmbuNyQud2Rut6SwN+aweCbwuGdw+KeweKkF4OfHQAAAadJREFUOMtjeEoAMAwfBQ9vYJO68RCu4BYDq1bXmr2XHoMlnlzet2qGpTgX8y2EFWwensEdrW2FbUAlJ2zDKsqaYwo6eZHcIGzoor/s5IdPSfefPt3nf3Xn3HZpPU8xJAXWIvU88VOffcm78vTptrijJe4OfAmy1kgKzjMlaIfUPvvQcObp03U120ucTZQaGc8je1NZrd8g8cL7eUufPp0wfX2mvVydripKONzkTG1JXvL6TuXTR/Zb5gR6F+Vw30QNqNUss/pqn3/MvrTD7Wy1V9Y0jtXoIZnLv2nSnrfzF3bP3hDRu1wwHzOo1VWOTXn5yi/q7MTJuxTUscTFQyHNc5vfbU1btOCgjNBDbJH1UF5i9/XP6VVrJRUfYo/Nx3YcpS/euOo4Pn6MqeDx44f3LoY2SYnOXBlUfPPug8cwRQxQ6Qf39sda+HqZaQiwGydFlx+4eQ+qAqrg4b3bPqbmNhkrNm5cbGUUHply7d6Dh8hWPH744Oa186eOHDl06NDh46fP33zwEMUEiCMe3L13GwjuPbj38PHTx9jT5OPHj5G9MFQyDgA8riWAv9eLFAAAAABJRU5ErkJggg==\") no-repeat #BBB; }");
+ out.println("#xmppinfo { background: #FFF; padding: 10px; display: none; }");
+
+ out.println("#signin { text-align: center; font-size: small; }");
+ out.println("#signinform { background: #FFF; padding: 10px 15px; margin-top: 15px; display: none; }");
+ out.println("input.txt { width: 212px; border: 1px solid #CCC; margin: 3px 0; padding: 3px; }");
+ out.println("input.submit { width: 70px; border: 1px solid #CCC; margin: 3px 0; padding: 3px; }");
+ out.println("</style>");
+ out.println("<link rel=\"icon\" href=\"//i.juick.com/favicon.png\"/>");
+ out.println("</head>");
+
+ out.println("<body>");
+
+ out.println("<ul id=\"tags\">");
+ out.println(" <li><a href=\"/tag/juick\" style=\"left: 359px; top: 120px; width: 311px; height: 99px\">juick</a></li>");
+ out.println(" <li><a href=\"/tag/linux\" style=\"left: 201px; top: 100px; width: 98px; height: 35px\">linux</a></li>");
+ out.println(" <li><a href=\"/tag/android\" style=\"left: 314px; top: 42px; width: 45px; height: 158px\">android</a></li>");
+ out.println(" <li><a href=\"/tag/работа\" style=\"left: 149px; top: 138px; width: 165px; height: 41px\">работа</a></li>");
+ out.println(" <li><a href=\"/tag/music\" style=\"left: 119px; top: 249px; width: 124px; height: 32px\">music</a></li>");
+ out.println(" <li><a href=\"/tag/windows\" style=\"left: 448px; top: 234px; width: 186px; height: 32px\">windows</a></li>");
+ out.println(" <li><a href=\"/tag/google\" style=\"left: 244px; top: 252px; width: 134px; height: 41px\">google</a></li>");
+ out.println(" <li><a href=\"/tag/кино\" style=\"left: 68px; top: 83px; width: 97px; height: 28px\">кино</a></li>");
+ out.println(" <li><a href=\"/tag/фото\" style=\"left: 400px; top: 266px; width: 101px; height: 29px\">фото</a></li>");
+ out.println(" <li><a href=\"/tag/жизнь\" style=\"left: 554px; top: 266px; width: 125px; height: 27px\">жизнь</a></li>");
+ out.println(" <li><a href=\"/tag/еда\" style=\"left: 46px; top: 196px; width: 71px; height: 32px\">еда</a></li>");
+ out.println(" <li><a href=\"/tag/музыка\" style=\"left: 61px; top: 111px; width: 139px; height: 27px\">музыка</a></li>");
+ out.println(" <li><a href=\"/tag/прекрасное\" style=\"left: 152px; top: 200px; width: 205px; height: 32px\">прекрасное</a></li>");
+ out.println(" <li><a href=\"/tag/книги\" style=\"left: 148px; top: 293px; width: 103px; height: 25px\">книги</a></li>");
+ out.println(" <li><a href=\"/tag/цитата\" style=\"left: 325px; top: 301px; width: 126px; height: 27px\">цитата</a></li> <li><a href=\"/tag/games\" style=\"left: 117px; top: 142px; width: 30px; height: 104px\">games</a></li>");
+ out.println(" <li><a href=\"/tag/ubuntu\" style=\"left: 503px; top: 2px; width: 28px; height: 102px\">ubuntu</a></li>");
+ out.println(" <li><a href=\"/tag/котэ\" style=\"left: 534px; top: 27px; width: 76px; height: 28px\">котэ</a></li>");
+ out.println(" <li><a href=\"/tag/ВНЕЗАПНО\" style=\"left: 501px; top: 293px; width: 146px; height: 23px\">ВНЕЗАПНО</a></li>");
+ out.println(" <li><a href=\"/tag/юмор\" style=\"left: 73px; top: 53px; width: 84px; height: 28px\">юмор</a></li>");
+ out.println(" <li><a href=\"/tag/мысли\" style=\"left: 202px; top: 179px; width: 102px; height: 21px\">мысли</a></li>");
+ out.println(" <li><a href=\"/tag/pic\" style=\"left: 400px; top: 78px; width: 33px; height: 38px\">pic</a></li>");
+ out.println(" <li><a href=\"/tag/политота\" style=\"left: 531px; top: 60px; width: 130px; height: 24px\">политота</a></li>");
+ out.println(" <li><a href=\"/tag/WOT\" style=\"left: 159px; top: 63px; width: 48px; height: 20px\">WOT</a></li>");
+ out.println(" <li><a href=\"/tag/fail\" style=\"left: 8px; top: 170px; width: 34px; height: 27px\">fail</a></li>");
+ out.println(" <li><a href=\"/tag/погода\" style=\"left: 670px; top: 126px; width: 24px; height: 93px\">погода</a></li>");
+ out.println(" <li><a href=\"/tag/apple\" style=\"left: 42px; top: 167px; width: 64px; height: 29px\">apple</a></li>");
+ out.println(" <li><a href=\"/tag/jabber\" style=\"left: 436px; top: 43px; width: 25px; height: 75px\">jabber</a></li>");
+ out.println(" <li><a href=\"/tag/тян\" style=\"left: 532px; top: 94px; width: 47px; height: 21px\">тян</a></li>");
+ out.println(" <li><a href=\"/tag/work\" style=\"left: 359px; top: 55px; width: 58px; height: 23px\">work</a></li>");
+ out.println(" <li><a href=\"/tag/Python\" style=\"left: 240px; top: 63px; width: 74px; height: 23px\">Python</a></li>");
+ out.println(" <li><a href=\"/tag/Видео\" style=\"left: 266px; top: 232px; width: 76px; height: 20px\">Видео</a></li>");
+ out.println(" <li><a href=\"/tag/авто\" style=\"left: 359px; top: 30px; width: 58px; height: 24px\">авто</a></li>");
+ out.println(" <li><a href=\"/tag/Anime\" style=\"left: 360px; top: 328px; width: 66px; height: 21px\">Anime</a></li>");
+ out.println(" <li><a href=\"/tag/игры\" style=\"left: 378px; top: 242px; width: 22px; height: 58px\">игры</a></li>");
+ out.println(" <li><a href=\"/tag/вело\" style=\"left: 176px; top: 9px; width: 18px; height: 54px\">вело</a></li>");
+ out.println(" <li><a href=\"/tag/web\" style=\"left: 661px; top: 219px; width: 22px; height: 47px\">web</a></li>");
+ out.println(" <li><a href=\"/tag/YouTube\" style=\"left: 498px; top: 316px; width: 81px; height: 24px\">YouTube</a></li>");
+ out.println(" <li><a href=\"/tag/Вопрос\" style=\"left: 208px; top: 18px; width: 22px; height: 72px\">Вопрос</a></li>");
+ out.println(" <li><a href=\"/tag/железо\" style=\"left: 159px; top: 318px; width: 75px; height: 16px\">железо</a></li>");
+ out.println(" <li><a href=\"/tag/Microsoft\" style=\"left: 20px; top: 146px; width: 86px; height: 21px\">Microsoft</a></li>");
+ out.println(" <li><a href=\"/tag/video\" style=\"left: 616px; top: 101px; width: 51px; height: 19px\">video</a></li>");
+ out.println(" <li><a href=\"/tag/Россия\" style=\"left: 32px; top: 242px; width: 68px; height: 16px\">Россия</a></li>");
+ out.println(" <li><a href=\"/tag/java\" style=\"left: 409px; top: 226px; width: 39px; height: 22px\">java</a></li>");
+ out.println(" <li><a href=\"/tag/новости\" style=\"left: 39px; top: 67px; width: 21px; height: 79px\">новости</a></li>");
+ out.println(" <li><a href=\"/tag/интернет\" style=\"left: 100px; top: 233px; width: 17px; height: 85px\">интернет</a></li>");
+ out.println(" <li><a href=\"/tag/steam\" style=\"left: 14px; top: 228px; width: 52px; height: 13px\">steam</a></li>");
+ out.println(" <li><a href=\"/tag/слова\" style=\"left: 501px; top: 272px; width: 51px; height: 18px\">слова</a></li>");
+ out.println(" <li><a href=\"/tag/почта\" style=\"left: 477px; top: 27px; width: 17px; height: 56px\">почта</a></li>");
+ out.println(" <li><a href=\"/tag/help\" style=\"left: 123px; top: 281px; width: 21px; height: 35px\">help</a></li>");
+ out.println(" <li><a href=\"/tag/skype\" style=\"left: 110px; top: 320px; width: 49px; height: 20px\">skype</a></li>");
+ out.println(" <li><a href=\"/tag/debian\" style=\"left: 461px; top: 47px; width: 16px; height: 51px\">debian</a></li>");
+ out.println(" <li><a href=\"/tag/win\" style=\"left: 505px; top: 104px; width: 27px; height: 16px\">win</a></li>");
+ out.println(" <li><a href=\"/tag/Религия\" style=\"left: 33px; top: 281px; width: 67px; height: 17px\">Религия</a></li>");
+ out.println(" <li><a href=\"/tag/soft\" style=\"left: 286px; top: 86px; width: 28px; height: 14px\">soft</a></li>");
+ out.println(" <li><a href=\"/tag/Политика\" style=\"left: 144px; top: 281px; width: 75px; height: 12px\">Политика</a></li>");
+ out.println(" <li><a href=\"/tag/сны\" style=\"left: 426px; top: 328px; width: 33px; height: 13px\">сны</a></li>");
+ out.println(" <li><a href=\"/tag/Питер\" style=\"left: 146px; top: 233px; width: 50px; height: 16px\">Питер</a></li>");
+ out.println(" <li><a href=\"/tag/bash\" style=\"left: 451px; top: 311px; width: 38px; height: 16px\">bash</a></li>");
+ out.println(" <li><a href=\"/tag/code\" style=\"left: 279px; top: 310px; width: 39px; height: 16px\">code</a></li>");
+ out.println(" <li><a href=\"/tag/yandex\" style=\"left: 19px; top: 263px; width: 56px; height: 18px\">yandex</a></li>");
+ out.println(" <li><a href=\"/tag/firefox\" style=\"left: 452px; top: 295px; width: 48px; height: 16px\">firefox</a></li>");
+ out.println(" <li><a href=\"/tag/hardware\" style=\"left: 230px; top: 40px; width: 67px; height: 18px\">hardware</a></li>");
+ out.println(" <li><a href=\"/tag/git\" style=\"left: 78px; top: 258px; width: 20px; height: 19px\">git</a></li>");
+ out.println(" <li><a href=\"/tag/dev\" style=\"left: 165px; top: 88px; width: 31px; height: 19px\">dev</a></li>");
+ out.println(" <li><a href=\"/tag/mobile\" style=\"left: 421px; top: 24px; width: 15px; height: 47px\">mobile</a></li>");
+ out.println(" <li><a href=\"/tag/люди\" style=\"left: 151px; top: 184px; width: 43px; height: 15px\">люди</a></li>");
+ out.println(" <li><a href=\"/tag/php\" style=\"left: 149px; top: 24px; width: 27px; height: 18px\">php</a></li>");
+ out.println(" <li><a href=\"/tag/haskell\" style=\"left: 271px; top: 293px; width: 48px; height: 16px\">haskell</a></li>");
+ out.println(" <li><a href=\"/tag/стихи\" style=\"left: 135px; top: 42px; width: 41px; height: 11px\">стихи</a></li>");
+ out.println(" <li><a href=\"/tag/photo\" style=\"left: 639px; top: 219px; width: 20px; height: 39px\">photo</a></li>");
+ out.println(" <li><a href=\"/tag/чай\" style=\"left: 448px; top: 220px; width: 27px; height: 14px\">чай</a></li>");
+ out.println(" <li><a href=\"/tag/Опрос\" style=\"left: 297px; top: 22px; width: 14px; height: 41px\">Опрос</a></li>");
+ out.println(" <li><a href=\"/tag/Chrome\" style=\"left: 311px; top: 25px; width: 48px; height: 17px\">Chrome</a></li>");
+ out.println(" <li><a href=\"/tag/life\" style=\"left: 255px; top: 311px; width: 23px; height: 16px\">life</a></li>");
+ out.println(" <li><a href=\"/tag/opera\" style=\"left: 226px; top: 232px; width: 38px; height: 14px\">opera</a></li>");
+ out.println(" <li><a href=\"/tag/programming\" style=\"left: 234px; top: 327px; width: 81px; height: 14px\">programming</a></li>");
+ out.println(" <li><a href=\"/tag/дети\" style=\"left: 15px; top: 197px; width: 31px; height: 13px\">дети</a></li>");
+ out.println(" <li><a href=\"/tag/сериалы\" style=\"left: 575px; top: 219px; width: 61px; height: 13px\">сериалы</a></li>");
+ out.println(" <li><a href=\"/tag/учеба\" style=\"left: 616px; top: 84px; width: 43px; height: 17px\">учеба</a></li>");
+ out.println("</ul>");
+
+ out.println("<div id=\"bottom1\">juick.com &copy; 2008-2014 &nbsp; <a href=\"/help/ru/contacts\" rel=\"nofollow\">Контакты</a> &#183; <a href=\"/help/\" rel=\"nofollow\">Помощь</a></div>");
+
+ out.println("<div id=\"signup\">");
+ out.println(" Зарегистрироваться:");
+ out.println(" <div id=\"facebook\"><a href=\"/_fblogin\" rel=\"nofollow\">Facebook</a></div>");
+ out.println(" <div id=\"vk\"><a href=\"/_vklogin\" rel=\"nofollow\">ВКонтакте</a></div>");
+ out.println(" <div id=\"xmpp\"><a href=\"#\" onclick=\"$('#xmppinfo').toggle(); return false\">XMPP</a>");
+ out.println(" <div id=\"xmppinfo\">Отправьте <b>LOGIN</b> на <a href=\"xmpp:juick@juick.com?message;body=LOGIN\">juick@juick.com</a></div>");
+ out.println(" </div>");
+ out.println("</div>");
+ out.println("<div id=\"signin\"><a href=\"#\" onclick=\"$('#signinform').toggle(); $('#nickinput').focus(); return false\">Уже зарегистрированы?</a>");
+ out.println("<div id=\"signinform\"><form action=\"/login\" method=\"POST\">");
+ out.println("<input class=\"txt\" type=\"text\" name=\"username\" placeholder=\"Имя пользователя\" id=\"nickinput\"/>");
+ out.println("<input class=\"txt\" type=\"password\" name=\"password\" placeholder=\"Пароль\"/>");
+ out.println("<input class=\"submit\" type=\"submit\" value=\"OK\"/>");
+ out.println("</form></div>");
+ out.println("</div>");
+
+ out.println("</body>");
+ out.println("</html>");
+ } finally {
+ out.close();
+ }
+ }
+
+ protected void doGetLogin(Connection sql, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ String hash = request.getQueryString();
+ if (hash.length() > 32) {
+ response.sendError(400);
+ return;
+ }
+
+ if (com.juick.server.UserQueries.getUIDbyHash(sql, hash) > 0) {
+ Cookie c = new Cookie("hash", hash);
+ c.setMaxAge(365 * 24 * 60 * 60);
+ response.addCookie(c);
+ response.sendRedirect("/");
+ } else {
+ response.sendError(403);
+ }
+ }
+
+ protected void doPostLogin(Connection sql, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ String username = request.getParameter("username");
+ String password = request.getParameter("password");
+ if (username == null || password == null || username.length() > 32 || password.isEmpty()) {
+ response.sendError(400);
+ return;
+ }
+
+ int uid = com.juick.server.UserQueries.checkPassword(sql, username, password);
+ if (uid > 0) {
+ String hash = com.juick.server.UserQueries.getHashByUID(sql, uid);
+ Cookie c = new Cookie("hash", hash);
+ c.setMaxAge(365 * 24 * 60 * 60);
+ response.addCookie(c);
+
+ String referer = request.getHeader("Referer");
+ if (referer != null && referer.startsWith("http://juick.com/") && !referer.equals("http://juick.com/login")) {
+ response.sendRedirect(referer);
+ } else {
+ response.sendRedirect("/");
+ }
+ } else {
+ response.sendError(403);
+ }
+ }
+
+ protected void doGetLogout(Connection sql, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+ if (visitor != null) {
+ PreparedStatement stmt = null;
+ try {
+ stmt = sql.prepareStatement("DELETE FROM logins WHERE user_id=?");
+ stmt.setInt(1, visitor.UID);
+ stmt.executeUpdate();
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(null, stmt);
+ }
+ }
+
+ Cookie c = new Cookie("hash", "-");
+ c.setDomain(".juick.com");
+ c.setMaxAge(0);
+ response.addCookie(c);
+
+ Cookie c2 = new Cookie("hash", "-");
+ c2.setMaxAge(0);
+ response.addCookie(c2);
+
+ response.sendRedirect("/");
+ }
+}
diff --git a/src/main/java/com/juick/http/www/Main.java b/src/main/java/com/juick/http/www/Main.java
new file mode 100644
index 00000000..56aa439f
--- /dev/null
+++ b/src/main/java/com/juick/http/www/Main.java
@@ -0,0 +1,322 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2011, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick.http.www;
+
+import com.juick.server.UserQueries;
+import com.juick.xmpp.JID;
+import com.juick.xmpp.Stream;
+import com.juick.xmpp.StreamComponent;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.URLEncoder;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Properties;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.MultipartConfig;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import ru.sape.Sape;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+@WebServlet(name = "Main", urlPatterns = {"/"})
+@MultipartConfig(fileSizeThreshold = 1024 * 1024, maxRequestSize = 1024 * 1024 * 10)
+public class Main extends HttpServlet implements Stream.StreamListener {
+
+ Connection sql;
+ Connection sqlSearch;
+ Stream xmpp;
+ Home home = new Home();
+ Discover discover = new Discover();
+ PM pm = new PM();
+ Login login = new Login();
+ Help help = new Help();
+ User pagesUser = new User();
+ UserThread pagesUserThread = new UserThread();
+ NewMessage pagesNewMessage = new NewMessage();
+ FacebookLogin loginFacebook = new FacebookLogin();
+ VKontakteLogin loginVK = new VKontakteLogin();
+ SignUp signup = new SignUp();
+ Settings settings = new Settings();
+ RSS rss = new RSS();
+
+ @Override
+ public void init() throws ServletException {
+ super.init();
+ try {
+ Properties conf = new Properties();
+ conf.load(new FileInputStream("/etc/juick/www.conf"));
+
+ Class.forName("com.mysql.jdbc.Driver");
+ sql = DriverManager.getConnection("jdbc:mysql://localhost/juick?autoReconnect=true&user=" + conf.getProperty("mysql_username", "") + "&password=" + conf.getProperty("mysql_password", ""));
+ sqlSearch = DriverManager.getConnection("jdbc:mysql://127.0.0.1:9306?autoReconnect=true&characterEncoding=utf8&maxAllowedPacket=512000", "", "");
+
+ setupXmppComponent(conf.getProperty("xmpp_password"));
+
+ PageTemplates.sape = new Sape(conf.getProperty("sape_user"), "juick.com", 2000, 3600);
+ } catch (Exception e) {
+ log(null, e);
+ }
+ }
+
+ public void setupXmppComponent(final String password) {
+ Thread thr = new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ Socket socket = new Socket("localhost", 5347);
+ xmpp = new StreamComponent(new JID("", "www.juick.com", ""), socket.getInputStream(), socket.getOutputStream(), password);
+ xmpp.addListener(Main.this);
+ xmpp.startParsing();
+ } catch (IOException e) {
+ System.err.println(e);
+ }
+ }
+ });
+ thr.start();
+ }
+
+ @Override
+ public void onStreamFail(String msg) {
+ System.err.println("XMPP STREAM FAIL: " + msg);
+ }
+
+ @Override
+ public void onStreamReady() {
+ System.err.println("XMPP STREAM READY");
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+ if (sql != null) {
+ try {
+ sql.close();
+ sql = null;
+ } catch (SQLException e) {
+ log(null, e);
+ }
+ }
+ if (sqlSearch != null) {
+ try {
+ sqlSearch.close();
+ sqlSearch = null;
+ } catch (SQLException e) {
+ log(null, e);
+ }
+ }
+ }
+
+ /**
+ * Handles the HTTP <code>GET</code> method.
+ * @param request servlet request
+ * @param response servlet response
+ * @throws ServletException if a servlet-specific error occurs
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ if (request.getCharacterEncoding() == null) {
+ request.setCharacterEncoding("UTF-8");
+ }
+ String uri = request.getRequestURI();
+
+ if (uri.equals("/")) {
+ String tag = request.getParameter("tag");
+ if (tag != null) {
+ Utils.sendPermanentRedirect(response, "/tag/" + URLEncoder.encode(tag, "UTF-8"));
+ } else {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+ home.doGet(sql, sqlSearch, request, response, visitor);
+ }
+ } else if (uri.equals("/post")) {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+ if (visitor != null) {
+ pagesNewMessage.doGetNewMessage(sql, request, response, visitor);
+ } else {
+ Utils.sendTemporaryRedirect(response, "/login");
+ }
+ } else if (uri.equals("/login")) {
+ if (request.getQueryString() == null) {
+ login.doGetLoginForm(sql, request, response);
+ } else {
+ login.doGetLogin(sql, request, response);
+ }
+ } else if (uri.startsWith("/pm/")) {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+ if (visitor == null) {
+ Utils.sendTemporaryRedirect(response, "/login");
+ } else {
+ if (uri.equals("/pm/inbox")) {
+ pm.doGetInbox(sql, request, response, visitor);
+ } else if (uri.equals("/pm/sent")) {
+ pm.doGetSent(sql, request, response, visitor);
+ } else {
+ Errors.doGet404(sql, request, response);
+ }
+ }
+ } else if (uri.startsWith("/rss/")) {
+ String uname = uri.substring(5);
+ int uid = UserQueries.getUIDbyName(sql, uname);
+ if (uid > 0) {
+ rss.doGet(sql, request, response, uid, uname);
+ } else {
+ response.sendError(404);
+ }
+ } else if (uri.equals("/logout")) {
+ login.doGetLogout(sql, request, response);
+ } else if (uri.equals("/settings")) {
+ settings.doGet(sql, request, response);
+ } else if (uri.equals("/_fblogin")) {
+ loginFacebook.doGet(sql, request, response);
+ } else if (uri.equals("/_vklogin")) {
+ loginVK.doGet(sql, request, response);
+ } else if (uri.equals("/signup")) {
+ signup.doGet(sql, request, response);
+ } else if (uri.equals("/help") || uri.equals("/help/")) {
+ help.doRedirectToHelpIndex(sql, request, response);
+ } else if (uri.startsWith("/help/")) {
+ help.doGetHelp(sql, request, response);
+ } else if (uri.startsWith("/tag/")) {
+ discover.doGet(sql, sqlSearch, request, response);
+ } else if (uri.matches("^/\\d+$")) {
+ String strID = request.getRequestURI().substring(1);
+ int mid = 0;
+ try {
+ mid = Integer.parseInt(strID);
+ } catch (NumberFormatException e) {
+ }
+ if (mid > 0) {
+ com.juick.User author = com.juick.server.MessagesQueries.getMessageAuthor(sql, mid);
+ if (author != null) {
+ Utils.sendPermanentRedirect(response, "/" + author.UName + "/" + mid);
+ return;
+ }
+ }
+ Errors.doGet404(sql, request, response);
+ } else if (uri.matches("^/[^/]+$")) {
+ com.juick.User user = com.juick.server.UserQueries.getUserByName(sql, request.getRequestURI().substring(1));
+ if (user != null) {
+ Utils.sendPermanentRedirect(response, "/" + user.UName + "/");
+ } else {
+ Errors.doGet404(sql, request, response);
+ }
+ } else if (uri.matches("^/.+/.*")) {
+ String uriparts[] = uri.split("/");
+ com.juick.User user = com.juick.server.UserQueries.getUserByName(sql, uriparts[1]);
+ if (user != null && user.UName.equals(uriparts[1]) && user.Banned == false) {
+ if (uriparts.length == 2) { // http://juick.com/username/
+ pagesUser.doGetBlog(sql, sqlSearch, request, response, user);
+ } else if (uriparts[2].equals("tags")) {
+ pagesUser.doGetTags(sql, request, response, user);
+ } else if (uriparts[2].equals("friends")) {
+ pagesUser.doGetFriends(sql, request, response, user);
+ } else if (uriparts[2].equals("readers")) {
+ pagesUser.doGetReaders(sql, request, response, user);
+ } else {
+ int mid = 0;
+ try {
+ mid = Integer.parseInt(uriparts[2]);
+ } catch (NumberFormatException e) {
+ }
+ if (mid > 0) {
+ com.juick.User author = com.juick.server.MessagesQueries.getMessageAuthor(sql, mid);
+ if (author != null) {
+ if (!author.UName.equals(user.UName)) {
+ Utils.sendPermanentRedirect(response, "/" + author.UName + "/" + mid);
+ } else {
+ pagesUserThread.doGetThread(sql, request, response, mid);
+ }
+ } else {
+ Errors.doGet404(sql, request, response);
+ }
+ } else {
+ Errors.doGet404(sql, request, response);
+ }
+ }
+ } else if (user != null && user.Banned == false) {
+ Utils.sendPermanentRedirect(response, "/" + user.UName + "/" + (uriparts.length > 2 ? uriparts[2] : ""));
+ } else {
+ Errors.doGet404(sql, request, response);
+ }
+ } else {
+ Errors.doGet404(sql, request, response);
+ }
+ }
+
+ /**
+ * Handles the HTTP <code>POST</code> method.
+ * @param request servlet request
+ * @param response servlet response
+ * @throws ServletException if a servlet-specific error occurs
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ if (request.getCharacterEncoding() == null) {
+ request.setCharacterEncoding("UTF-8");
+ }
+
+ String uri = request.getRequestURI();
+ if (uri.equals("/post")) {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+ if (visitor != null) {
+ pagesNewMessage.doPostMessage(sql, request, response, xmpp, visitor);
+ } else {
+ response.sendError(403);
+ }
+ } else if (uri.equals("/comment")) {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+ if (visitor != null) {
+ pagesNewMessage.doPostComment(sql, request, response, xmpp, visitor);
+ } else {
+ response.sendError(403);
+ }
+ } else if (uri.equals("/like")) {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+ if (visitor != null) {
+ pagesNewMessage.doPostRecomm(sql, request, response, xmpp, visitor);
+ } else {
+ response.sendError(403);
+ }
+ } else if (uri.equals("/pm/send")) {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+ if (visitor != null) {
+ pm.doPostPM(sql, request, response, xmpp, visitor);
+ } else {
+ response.sendError(403);
+ }
+ } else if (uri.equals("/login")) {
+ login.doPostLogin(sql, request, response);
+ } else if (uri.equals("/signup")) {
+ signup.doPost(sql, request, response);
+ } else if (uri.equals("/settings")) {
+ settings.doPost(sql, request, response);
+ } else {
+ response.sendError(405);
+ }
+ }
+}
diff --git a/src/main/java/com/juick/http/www/NewMessage.java b/src/main/java/com/juick/http/www/NewMessage.java
new file mode 100644
index 00000000..34733511
--- /dev/null
+++ b/src/main/java/com/juick/http/www/NewMessage.java
@@ -0,0 +1,420 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2011, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick.http.www;
+
+import com.juick.Tag;
+import com.juick.server.CrosspostQueries;
+import com.juick.server.MessagesQueries;
+import com.juick.server.SubscriptionsQueries;
+import com.juick.server.TagQueries;
+import com.juick.server.UserQueries;
+import com.juick.xmpp.JID;
+import com.juick.xmpp.Message;
+import com.juick.xmpp.Stream;
+import com.juick.xmpp.extensions.JuickMessage;
+import com.juick.xmpp.extensions.JuickUser;
+import com.juick.xmpp.extensions.Nickname;
+import com.juick.xmpp.extensions.XOOB;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.sql.Connection;
+import java.util.ArrayList;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class NewMessage {
+
+ protected void doGetNewMessage(Connection sql, HttpServletRequest request, HttpServletResponse response, com.juick.User visitor) throws ServletException, IOException {
+ response.setContentType("text/html; charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ try {
+ PageTemplates.pageHead(out, "Написать", "<script src=\"//maps.google.com/maps?file=api&amp;v=2&amp;sensor=false&amp;key=ABQIAAAAVVtPtxkw4soCEHg44FsNChRB4OFYjAXt73He16Zkp6a_0tPs2RTU6i6UlcMs4QvPBYvIY8rWvcxqOg\" type=\"text/javascript\"></script>"
+ + "<script src=\"//static.juick.com/mc.js\" type=\"text/javascript\" defer=\"defer\"></script>"
+ + "<script src=\"//static.juick.com/maps.js?2010111500\" type=\"text/javascript\" defer=\"defer\"></script>"
+ + "<script src=\"//static.juick.com/post3.js\" type=\"text/javascript\" defer=\"defer\"></script>");
+ PageTemplates.pageNavigation(out, visitor, null);
+
+ out.println("<section id=\"content\" class=\"pagetext\">");
+ out.println("<form action=\"/post2\" method=\"post\" id=\"postmsg\" enctype=\"multipart/form-data\">");
+ out.println("<p style=\"text-align: left\"><b>Место: <span id=\"location\"></span></b> <span id=\"locationclear\">&mdash; <a href=\"#\" onclick=\"clearLocation()\">Отменить</a></span></p>");
+ out.println("<p style=\"text-align: left\"><b>Фото:</b> <span id=\"attachmentfile\"><input type=\"file\" name=\"attach\"/> <i>(JPG, PNG, до 10Мб)</i></span></p>");
+
+ String body = request.getParameter("body");
+ if (body == null) {
+ body = "";
+ } else {
+ if (body.length() > 4096) {
+ body = body.substring(0, 4096);
+ }
+ body = Utils.encodeHTML(body);
+ }
+ out.println("<p><textarea name=\"body\" class=\"newmessage\" rows=\"7\" cols=\"10\">" + body + "</textarea><br/>");
+
+ out.println("<input type=\"hidden\" name=\"place_id\"/>" + "" + "<input type=\"submit\" class=\"subm\" value=\" Отправить \"/></p>");
+ out.println("</form>");
+ out.println("<div id=\"geomap\"></div>");
+ out.println("<p style=\"text-align: left\"><b>Теги:</b></p>");
+ printUserTags(sql, out, visitor);
+ out.println("</section>");
+
+ PageTemplates.pageFooter(request, out, visitor, false);
+ PageTemplates.pageEnd(out);
+ } finally {
+ out.close();
+ }
+ }
+
+ void printUserTags(Connection sql, PrintWriter out, com.juick.User visitor) {
+ ArrayList<Tag> tags = TagQueries.getUserTagsAll(sql, visitor.UID);
+
+ if (tags.isEmpty()) {
+ return;
+ }
+
+ int min = tags.get(0).UsageCnt;
+ int max = tags.get(0).UsageCnt;
+ for (int i = 1; i < tags.size(); i++) {
+ int usagecnt = tags.get(i).UsageCnt;
+ if (usagecnt < min) {
+ min = usagecnt;
+ }
+ if (usagecnt > max) {
+ max = usagecnt;
+ }
+ }
+ max -= min;
+
+ out.print("<p style=\"text-align: justify\">");
+ for (int i = 0; i < tags.size(); i++) {
+ if (i > 0) {
+ out.print(" ");
+ }
+ String taglink = "";
+ try {
+ taglink = "<a onclick=\"return addTag('" + Utils.encodeHTML(tags.get(i).Name) + "')\" href=\"/" + visitor.UName + "/?tag=" + URLEncoder.encode(tags.get(i).Name, "utf-8") + "\" title=\"" + tags.get(i).UsageCnt + "\">" + Utils.encodeHTML(tags.get(i).Name) + "</a>";
+ } catch (UnsupportedEncodingException e) {
+ }
+ int usagecnt = tags.get(i).UsageCnt;
+ if (usagecnt <= max / 5 + min) {
+ out.print("<span style=\"font-size: small\">" + taglink + "</span>");
+ } else if (usagecnt <= max / 5 * 2 + min) {
+ out.print(taglink);
+ } else if (usagecnt <= max / 5 * 3 + min) {
+ out.print("<span style=\"font-size: large\">" + taglink + "</span>");
+ } else if (usagecnt <= max / 5 * 4 + min) {
+ out.print("<span style=\"font-size: x-large\">" + taglink + "</span>");
+ } else {
+ out.print("<span style=\"font-size: xx-large\">" + taglink + "</span>");
+ }
+ }
+ out.println("</p>");
+ }
+
+ public void doPostMessage(Connection sql, HttpServletRequest request, HttpServletResponse response, Stream xmpp, com.juick.User visitor) throws ServletException, IOException {
+ String body = request.getParameter("body");
+ if (body == null || body.length() < 1 || body.length() > 4096) {
+ response.sendError(400);
+ return;
+ }
+ body = body.replace("\r", "");
+
+ String tagsStr = request.getParameter("tags");
+ if (tagsStr == null || tagsStr.isEmpty()) {
+ response.sendError(400);
+ return;
+ }
+ String tagsArr[] = tagsStr.split("[ \\,]");
+ for (int i = 0; i < tagsArr.length; i++) {
+ if (tagsArr[i].startsWith("*")) {
+ tagsArr[i] = tagsArr[i].substring(1);
+ }
+ if (tagsArr[i].length() > 64) {
+ tagsArr[i] = tagsArr[i].substring(0, 64);
+ }
+ }
+ ArrayList<com.juick.Tag> tags = TagQueries.getTags(sql, tagsArr, true);
+ if (tags.isEmpty()) {
+ response.sendError(400);
+ return;
+ }
+ while (tags.size() > 5) {
+ tags.remove(5);
+ }
+
+ String attachmentFName = null;
+ try {
+ attachmentFName = Utils.receiveMultiPartFile(request, "attach");
+ } catch (Exception e) {
+ System.out.println("MULTIPART ERROR: " + e.toString());
+ response.sendError(400);
+ return;
+ }
+
+ String paramImg = request.getParameter("img");
+ if (attachmentFName == null && paramImg != null && paramImg.length() > 12 && paramImg.startsWith("http://") && !paramImg.equals("http://")) {
+ try {
+ attachmentFName = Utils.downloadImage(paramImg);
+ } catch (Exception e) {
+ System.out.println("DOWNLOAD ERROR: " + e.toString());
+ response.sendError(500);
+ return;
+ }
+ }
+
+ String attachmentType = attachmentFName != null ? attachmentFName.substring(attachmentFName.length() - 3) : null;
+ int mid = MessagesQueries.createMessage(sql, visitor.UID, body, attachmentType, tags);
+ SubscriptionsQueries.subscribeMessage(sql, mid, visitor.UID);
+
+ Message xmsg = new Message();
+ xmsg.from = new JID("juick", "juick.com", null);
+ xmsg.type = Message.Type.chat;
+ xmsg.thread = "juick-" + mid;
+
+ JuickMessage jmsg = new JuickMessage(MessagesQueries.getMessage(sql, mid));
+ xmsg.addChild(jmsg);
+
+ Nickname nick = new Nickname();
+ nick.Nickname = "@" + jmsg.User.UName;
+ xmsg.addChild(nick);
+
+ if (attachmentFName != null) {
+ String fname = mid + "." + attachmentType;
+ String attachmentURL = "http://i.juick.com/photos-1024/" + fname;
+
+ Runtime.getRuntime().exec("/var/www/juick.com/cgi/p-convert.sh /var/www/juick.com/i/tmp/" + attachmentFName + " " + fname);
+
+ body = attachmentURL + "\n" + body;
+ XOOB xoob = new XOOB();
+ xoob.URL = attachmentURL;
+ xmsg.addChild(xoob);
+ }
+
+ String tagsStr2 = "";
+ for (int i = 0; i < tagsArr.length; i++) {
+ tagsStr2 += " *" + tagsArr[i];
+ }
+ xmsg.body = "@" + jmsg.User.UName + ":" + tagsStr2 + "\n" + body + "\n\n#" + mid + " http://juick.com/" + mid;
+
+ xmsg.to = new JID("juick", "s2s.juick.com", null);
+ xmpp.send(xmsg);
+
+ xmsg.to.Host = "ws.juick.com";
+ xmpp.send(xmsg);
+
+ xmsg.to.Host = "push.juick.com";
+ xmpp.send(xmsg);
+
+ xmsg.to.Host = "crosspost.juick.com";
+ xmsg.to.Username = "twitter";
+ xmpp.send(xmsg);
+ xmsg.to.Username = "fb";
+ xmpp.send(xmsg);
+
+ xmsg.to.Host = "nologin.ru";
+ xmsg.to.Username = "jubo";
+ xmpp.send(xmsg);
+
+ //
+
+ response.setContentType("text/html; charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ try {
+ PageTemplates.pageHead(out, "Сообщение опубликовано", null);
+ PageTemplates.pageNavigation(out, visitor, null);
+ PageTemplates.pageHomeColumn(out, sql, visitor);
+
+ String hashtags = "";
+ String tagscomma = "";
+ for (int i = 0; i < tagsArr.length; i++) {
+ if (i > 0) {
+ hashtags += " ";
+ tagscomma += ",";
+ }
+ hashtags += "#" + tagsArr[i];
+ tagscomma += tagsArr[i];
+ }
+
+ String url = URLEncoder.encode("http://juick.com/" + mid, "utf-8");
+ String sharetwi = hashtags + " " + body;
+ if (sharetwi.length() > 115) {
+ sharetwi = sharetwi.substring(0, 114) + "…";
+ }
+ sharetwi += " http://juick.com/" + mid;
+ String sharelj = URLEncoder.encode(body + "\n", "utf-8") + url;
+
+ out.println("<section id=\"content\">");
+ out.println("<h1>Сообщение опубликовано</h1>");
+ out.println("<p>Поделитесь своим новым постом в социальных сетях:</p>");
+ if (CrosspostQueries.getTwitterTokens(sql, visitor.UID) == null) {
+ out.println("<p><a href=\"https://twitter.com/intent/tweet?text=" + URLEncoder.encode(sharetwi, "utf-8") + "\" onclick=\"return openSocialWindow(this)\" class=\"ico32-twi sharenew\">Отправить в Twitter</a></p>");
+ }
+ out.println("<p><a href=\"http://www.livejournal.com/update.bml?subject=" + URLEncoder.encode(hashtags, "utf-8") + "&event=" + sharelj + "&prop_taglist=" + URLEncoder.encode(tagscomma, "utf-8") + "\" target=\"_blank\" class=\"ico32-lj sharenew\">Отправить в LiveJournal</a></p>");
+ out.println("<p><a href=\"https://vk.com/share.php?url=" + url + "\" onclick=\"return openSocialWindow(this)\" class=\"ico32-vk sharenew\">Отправить в ВКонтакте</a></p>");
+ if (CrosspostQueries.getFacebookToken(sql, visitor.UID) == null) {
+ out.println("<p><a href=\"https://www.facebook.com/sharer/sharer.php?u=" + url + "\" onclick=\"return openSocialWindow(this)\" class=\"ico32-fb sharenew\">Отправить в Facebook</a></p>");
+ }
+ out.println("<p><a href=\"https://plus.google.com/share?url=" + url + "\" onclick=\"return openSocialWindow(this)\" class=\"ico32-gp sharenew\">Отправить в Google+</a></p>");
+ out.println("<p>Ссылка на сообщение: <a href=\"http://juick.com/" + mid + "\">http://juick.com/" + mid + "</a></p>");
+ out.println("</section>");
+
+ PageTemplates.pageFooter(request, out, visitor, false);
+ PageTemplates.pageEnd(out);
+ } finally {
+ out.close();
+ }
+ }
+
+ public void doPostComment(Connection sql, HttpServletRequest request, HttpServletResponse response, Stream xmpp, com.juick.User visitor) throws ServletException, IOException {
+ int mid = Utils.parseInt(request.getParameter("mid"), 0);
+ if (mid == 0) {
+ response.sendError(400);
+ return;
+ }
+ com.juick.Message msg = MessagesQueries.getMessage(sql, mid);
+ if (msg == null) {
+ response.sendError(404);
+ return;
+ }
+
+ int rid = Utils.parseInt(request.getParameter("rid"), 0);
+ com.juick.Message reply = null;
+ if (rid > 0) {
+ reply = MessagesQueries.getReply(sql, mid, rid);
+ if (reply == null) {
+ response.sendError(404);
+ return;
+ }
+ }
+
+ String body = request.getParameter("body");
+ if (body == null || body.length() < 1 || body.length() > 4096) {
+ response.sendError(400);
+ return;
+ }
+ body = body.replace("\r", "");
+
+ if ((msg.ReadOnly && msg.User.UID != visitor.UID) || UserQueries.isInBLAny(sql, msg.User.UID, visitor.UID) || (reply != null && UserQueries.isInBLAny(sql, reply.User.UID, visitor.UID))) {
+ response.sendError(403);
+ return;
+ }
+
+ String attachmentFName = null;
+ try {
+ attachmentFName = Utils.receiveMultiPartFile(request, "attach");
+ } catch (Exception e) {
+ System.out.println("MULTIPART ERROR: " + e.toString());
+ response.sendError(400);
+ return;
+ }
+
+ String paramImg = request.getParameter("img");
+ if (attachmentFName == null && paramImg != null && paramImg.length() > 12 && paramImg.startsWith("http://") && !paramImg.equals("http://")) {
+ try {
+ attachmentFName = Utils.downloadImage(paramImg);
+ } catch (Exception e) {
+ System.out.println("DOWNLOAD ERROR: " + e.toString());
+ response.sendError(500);
+ return;
+ }
+ }
+
+ String attachmentType = attachmentFName != null ? attachmentFName.substring(attachmentFName.length() - 3) : null;
+ int ridnew = MessagesQueries.createReply(sql, mid, rid, visitor.UID, body, attachmentType);
+ SubscriptionsQueries.subscribeMessage(sql, mid, visitor.UID);
+
+ Message xmsg = new Message();
+ xmsg.from = new JID("juick", "juick.com", null);
+ xmsg.type = Message.Type.chat;
+ xmsg.thread = "juick-" + mid;
+
+ JuickMessage jmsg = new JuickMessage(MessagesQueries.getReply(sql, mid, ridnew));
+ xmsg.addChild(jmsg);
+
+ String quote = reply != null ? reply.Text : msg.Text;
+ if (quote.length() >= 50) {
+ quote = quote.substring(0, 47) + "...";
+ }
+
+ Nickname nick = new Nickname();
+ nick.Nickname = "@" + jmsg.User.UName;
+ xmsg.addChild(nick);
+
+ if (attachmentFName != null) {
+ String fname = mid + "-" + ridnew + "." + attachmentType;
+ String attachmentURL = "http://i.juick.com/photos-1024/" + fname;
+
+ Runtime.getRuntime().exec("/var/www/juick.com/cgi/p-convert.sh /var/www/juick.com/i/tmp/" + attachmentFName + " " + fname);
+
+ body = attachmentURL + "\n" + body;
+ XOOB xoob = new XOOB();
+ xoob.URL = attachmentURL;
+ xmsg.addChild(xoob);
+ }
+
+ xmsg.body = "Reply by @" + jmsg.User.UName + ":\n>" + quote + "\n" + body + "\n\n#" + mid + "/" + ridnew + " http://juick.com/" + mid + "#" + ridnew;
+
+ xmsg.to = new JID("juick", "s2s.juick.com", null);
+ xmpp.send(xmsg);
+
+ xmsg.to.Host = "ws.juick.com";
+ xmpp.send(xmsg);
+
+ Utils.sendTemporaryRedirect(response, "/" + msg.User.UName + "/" + mid + "#" + ridnew);
+ }
+
+ public void doPostRecomm(Connection sql, HttpServletRequest request, HttpServletResponse response, Stream xmpp, com.juick.User visitor) throws ServletException, IOException {
+ int mid = Utils.parseInt(request.getParameter("mid"), 0);
+ if (mid == 0) {
+ response.sendError(400);
+ return;
+ }
+ com.juick.Message msg = MessagesQueries.getMessage(sql, mid);
+ if (msg == null) {
+ response.sendError(404);
+ return;
+ }
+ if (msg.User.UID == visitor.UID) {
+ response.sendError(403);
+ return;
+ }
+
+ boolean res = MessagesQueries.recommendMessage(sql, mid, visitor.UID);
+
+ if (res) {
+ Message xmsg = new Message();
+ xmsg.from = new JID("juick", "juick.com", null);
+ xmsg.to = new JID("recomm", "s2s.juick.com", null);
+ JuickMessage jmsg = new JuickMessage();
+ jmsg.MID = mid;
+ jmsg.User = new JuickUser(visitor);
+ xmsg.addChild(jmsg);
+ xmpp.send(xmsg);
+
+ Utils.replyJSON(request, response, "{\"status\":\"ok\"}");
+ } else {
+ response.sendError(500);
+ }
+ }
+}
diff --git a/src/main/java/com/juick/http/www/PM.java b/src/main/java/com/juick/http/www/PM.java
new file mode 100644
index 00000000..d0d2f514
--- /dev/null
+++ b/src/main/java/com/juick/http/www/PM.java
@@ -0,0 +1,231 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2011, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick.http.www;
+
+import com.juick.server.PMQueries;
+import com.juick.server.UserQueries;
+import com.juick.xmpp.JID;
+import com.juick.xmpp.Message;
+import com.juick.xmpp.Stream;
+import com.juick.xmpp.extensions.JuickMessage;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.util.ArrayList;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class PM {
+
+ protected void doGetInbox(Connection sql, HttpServletRequest request, HttpServletResponse response, com.juick.User visitor) throws ServletException, IOException {
+ /*
+ int paramBefore = 0;
+ String paramBeforeStr = request.getParameter("before");
+ if (paramBeforeStr != null) {
+ try {
+ paramBefore = Integer.parseInt(paramBeforeStr);
+ } catch (NumberFormatException e) {
+ }
+ }
+ */
+
+ String title = "PM: Inbox";
+ ArrayList<com.juick.Message> msgs = PMQueries.getLastPMInbox(sql, visitor.UID);
+
+ response.setContentType("text/html; charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ try {
+ PageTemplates.pageHead(out, title, null);
+ PageTemplates.pageNavigation(out, visitor, null);
+ PageTemplates.pageHomeColumn(out, sql, visitor);
+
+ out.println("<section id=\"content\">");
+
+ if (!msgs.isEmpty()) {
+ out.println("<ul>");
+ for (int i = msgs.size() - 1; i >= 0; i--) {
+ com.juick.Message msg = msgs.get(i);
+
+ String txt = PageTemplates.formatMessage(msg.Text);
+
+ out.println(" <li class=\"msg\">");
+ out.println(" <div class=\"msg-avatar\"><a href=\"/" + msg.User.UName + "/\"><img src=\"//i.juick.com/a/" + msg.User.UID + ".png\" alt=\"" + msg.User.UName + "\"/></a></div>");
+ out.println(" <div class=\"msg-cont\">");
+ out.println(" <div class=\"msg-header\"><a href=\"/" + msg.User.UName + "/\">@" + msg.User.UName + "</a>:</div>");
+ out.println(" <div class=\"msg-ts\"><a href=\"#\" onclick=\"return false\" title=\"" + msg.TimestampString + " GMT\">" + PageTemplates.formatDate(msg.TimeAgo, msg.TimestampString) + "</a></div>");
+ out.println(" <div class=\"msg-txt\">" + txt + "</div>");
+
+ out.println(" <form action=\"/pm/send\" method=\"POST\" enctype=\"multipart/form-data\"><input type=\"hidden\" name=\"uname\" value=\"" + msg.User.UName + "\"/>");
+ out.println(" <div class=\"msg-comment\"><div class=\"ta-wrapper\"><textarea name=\"body\" rows=\"1\" class=\"replypm\" placeholder=\"Написать ответ\" onkeypress=\"postformListener(this.form,event)\"></textarea></div></div>");
+ out.println(" </form>");
+
+ out.println(" </div>");
+ out.println(" </li>");
+ }
+ out.println("</ul>");
+ }
+
+ /*
+ if (msgs.size() >= 20) {
+ String nextpage = "?before=" + msgs.get(msgs.size() - 1);
+ out.println("<p class=\"page\"><a href=\"" + nextpage + "\">Читать дальше →</a></p>");
+ }
+ */
+
+ out.println("</section>");
+
+ PageTemplates.pageFooter(request, out, visitor, false);
+ PageTemplates.pageEnd(out);
+ } finally {
+ out.close();
+ }
+ }
+
+ protected void doGetSent(Connection sql, HttpServletRequest request, HttpServletResponse response, com.juick.User visitor) throws ServletException, IOException {
+ /*
+ int paramBefore = 0;
+ String paramBeforeStr = request.getParameter("before");
+ if (paramBeforeStr != null) {
+ try {
+ paramBefore = Integer.parseInt(paramBeforeStr);
+ } catch (NumberFormatException e) {
+ }
+ }
+ */
+
+ String title = "PM: Sent";
+ ArrayList<com.juick.Message> msgs = PMQueries.getLastPMSent(sql, visitor.UID);
+
+ String uname = request.getParameter("uname");
+ if (!UserQueries.checkUserNameValid(uname)) {
+ uname = "";
+ }
+
+ response.setContentType("text/html; charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ try {
+ PageTemplates.pageHead(out, title, null);
+ PageTemplates.pageNavigation(out, visitor, null);
+ PageTemplates.pageHomeColumn(out, sql, visitor);
+
+ out.println("<section id=\"content\">");
+
+ out.println("<form action=\"/pm/send\" method=\"POST\" enctype=\"multipart/form-data\">");
+ out.println("<div class=\"newpm\">");
+ out.println(" <div class=\"newpm-to\">To: <input type=\"text\" name=\"uname\" placeholder=\"username\" value=\"" + uname + "\"/></div>");
+ out.println(" <div class=\"newpm-body\"><textarea name=\"body\" rows=\"2\" onkeypress=\"postformListener(this.form,event)\"></textarea></div>");
+ out.println(" <div class=\"newpm-send\"><input type=\"submit\" value=\"OK\"/></div>");
+ out.println("</div>");
+ out.println("</form>");
+
+ if (!msgs.isEmpty()) {
+ out.println("<ul>");
+ for (int i = msgs.size() - 1; i >= 0; i--) {
+ com.juick.Message msg = msgs.get(i);
+
+ String txt = PageTemplates.formatMessage(msg.Text);
+
+ out.println(" <li class=\"msg\">");
+ out.println(" <div class=\"msg-avatar\"><img src=\"//i.juick.com/a/" + visitor.UID + ".png\"/></div>");
+ out.println(" <div class=\"msg-cont\">");
+ out.println(" <div class=\"msg-header\">→ <a href=\"/" + msg.User.UName + "/\">@" + msg.User.UName + "</a>:</div>");
+ out.println(" <div class=\"msg-ts\"><a href=\"#\" onclick=\"return false\" title=\"" + msg.TimestampString + " GMT\">" + PageTemplates.formatDate(msg.TimeAgo, msg.TimestampString) + "</a></div>");
+ out.println(" <div class=\"msg-txt\">" + txt + "</div>");
+ out.println(" </div>");
+ out.println(" </li>");
+ }
+ out.println("</ul>");
+ }
+
+ /*
+ if (msgs.size() >= 20) {
+ String nextpage = "?before=" + msgs.get(msgs.size() - 1);
+ out.println("<p class=\"page\"><a href=\"" + nextpage + "\">Читать дальше →</a></p>");
+ }
+ */
+
+ out.println("</section>");
+
+ PageTemplates.pageFooter(request, out, visitor, false);
+ PageTemplates.pageEnd(out);
+ } finally {
+ out.close();
+ }
+ }
+
+ public void doPostPM(Connection sql, HttpServletRequest request, HttpServletResponse response, Stream xmpp, com.juick.User visitor) throws ServletException, IOException {
+ String uname = request.getParameter("uname");
+ if (uname.startsWith("@")) {
+ uname = uname.substring(1);
+ }
+ int uid = 0;
+ if (UserQueries.checkUserNameValid(uname)) {
+ uid = UserQueries.getUIDbyName(sql, uname);
+ }
+
+ String body = request.getParameter("body");
+ if (uid == 0 || body == null || body.length() < 1 || body.length() > 10240) {
+ response.sendError(400);
+ return;
+ }
+
+ if (UserQueries.isInBLAny(sql, uid, visitor.UID)) {
+ response.sendError(403);
+ return;
+ }
+
+ if (PMQueries.createPM(sql, visitor.UID, uid, body)) {
+ Message msg = new Message();
+ msg.from = new JID("juick", "juick.com", null);
+ msg.to = new JID(Integer.toString(uid), "push.juick.com", null);
+ JuickMessage jmsg = new JuickMessage();
+ jmsg.User = UserQueries.getUserByUID(sql, visitor.UID);
+ jmsg.Text = body;
+ msg.childs.add(jmsg);
+ xmpp.send(msg);
+
+ msg.to.Host = "ws.juick.com";
+ xmpp.send(msg);
+
+ String jid = UserQueries.getJIDbyUID(sql, uid);
+ if (jid != null) {
+ Message mm = new Message();
+ mm.to = new JID(jid);
+ mm.type = Message.Type.chat;
+ if (PMQueries.havePMinRoster(sql, visitor.UID, jid)) {
+ mm.from = new JID(jmsg.User.UName, "juick.com", "Juick");
+ mm.body = body;
+ } else {
+ mm.from = new JID("juick", "juick.com", "Juick");
+ mm.body = "Private message from @" + jmsg.User.UName + ":\n" + body;
+ }
+ xmpp.send(mm);
+ }
+
+ Utils.sendTemporaryRedirect(response, "/pm/sent");
+
+ } else {
+ response.sendError(500);
+ }
+ }
+}
diff --git a/src/main/java/com/juick/http/www/PageTemplates.java b/src/main/java/com/juick/http/www/PageTemplates.java
new file mode 100644
index 00000000..7d6bd1c8
--- /dev/null
+++ b/src/main/java/com/juick/http/www/PageTemplates.java
@@ -0,0 +1,584 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2011, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick.http.www;
+
+import com.juick.server.MessagesQueries;
+import com.juick.server.UserQueries;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.servlet.http.HttpServletRequest;
+import ru.sape.Sape;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class PageTemplates {
+
+ public static Sape sape = null;
+ private static final SimpleDateFormat sdfSQL = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ private static SimpleDateFormat sdfSimple = new SimpleDateFormat("d MMM");
+ private static SimpleDateFormat sdfFull = new SimpleDateFormat("d MMM yyyy");
+ private static String tagsHTML = null;
+
+ public static void pageHead(PrintWriter out, String title, String headers) {
+ out.println("<!DOCTYPE html>");
+ out.print("<html>");
+ out.print("<head>");
+ out.print("<link rel=\"stylesheet\" href=\"/style.2014072500.css\"/>");
+ out.print("<script type=\"text/javascript\" src=\"//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js\"></script>");
+ out.print("<script type=\"text/javascript\" src=\"/scripts.2014072900.js\"></script>");
+ if (headers != null) {
+ out.print(headers);
+ }
+ out.print("<title>" + title + "</title>");
+ out.print("<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,user-scalable=no\"/>");
+ out.println("<link rel=\"icon\" href=\"//i.juick.com/favicon.png\"/>");
+ out.println("<!--[if lt IE 9]>");
+ out.println("<script src=\"//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7/html5shiv.min.js\"></script>");
+ out.println("<![endif]-->");
+ out.println("</head>");
+ out.flush();
+ out.println("<body>");
+ }
+
+ public static void pageNavigation(PrintWriter out, com.juick.User visitor, String search) {
+ out.println("<header>");
+ out.println(" <div id=\"logo\"><a href=\"/\">Juick</a></div>");
+ out.print(" <nav id=\"global\"><ul>");
+ out.print("<li><a href=\"/\">Популярные</a></li>");
+ out.print("<li><a href=\"/?show=all\" rel=\"nofollow\">Все сообщения</a></li>");
+ out.print("<li><a href=\"/reader\" rel=\"nofollow\">Ссылки</a></li>");
+ out.println("</ul></nav>");
+ out.print(" <div id=\"search\"><form action=\"/\"><input type=\"text\" name=\"search\" class=\"text\" placeholder=\"Поиск\"");
+ if (search != null) {
+ out.print(" value=\"" + Utils.encodeHTML(search) + "\"");
+ }
+ out.println("/></form></div>");
+ out.println(" <section id=\"headdiv\">");
+ if (visitor != null) {
+ out.print(" <nav id=\"user\"><ul>");
+ out.print("<li><a href=\"/?show=my\">Моя лента</a></li>");
+ out.print("<li><a href=\"/pm/inbox\">Приватные</a></li>");
+ out.print("<li><a href=\"/?show=discuss\">Обсуждения</a></li>");
+ out.print("<li><a href=\"/?show=recommended\">Рекомендации</a></li>");
+ out.println("</ul></nav>");
+ out.print(" <nav id=\"actions\"><ul>");
+ out.print("<li><a href=\"/#post\">Написать</a></li>");
+ out.print("<li><a href=\"/" + visitor.UName + "\">@" + visitor.UName + "</a></li>");
+ out.print("<li><a href=\"/logout\">Выйти</a></li>");
+ out.println("</ul></nav>");
+ } else {
+ out.println("<p>Чтобы добавлять сообщения и комментарии, <a href=\"#\" onclick=\"return openDialogLogin()\">представьтесь</a>.</p>");
+ }
+ out.println(" </section>");
+ out.println("</header>");
+ }
+
+ public static void pageYandexAd728(PrintWriter out, int YandexID) {
+ /*
+ out.println("<div id=\"yandex_ad_728\"></div>");
+ out.println("<script type=\"text/javascript\">");
+ out.println("if($(window).width()>1000) {");
+ out.println("(function(w, d, n, s, t) {");
+ out.println("w[n] = w[n] || [];");
+ out.println("w[n].push(function() {");
+ out.println("Ya.Direct.insertInto(84715, \"yandex_ad_728\", {");
+ out.println("stat_id: " + YandexID + ",");
+ out.println("site_charset: \"utf-8\",");
+ out.println("ad_format: \"direct\",");
+ out.println("type: \"728x90\",");
+ out.println("site_bg_color: \"FFFFFF\",");
+ out.println("title_color: \"006699\",");
+ out.println("url_color: \"000000\",");
+ out.println("text_color: \"000000\",");
+ out.println("hover_color: \"006699\"");
+ out.println("});");
+ out.println("});");
+ out.println("t = d.getElementsByTagName(\"script\")[0]");
+ out.println("s = d.createElement(\"script\");");
+ out.println("s.type = \"text/javascript\";");
+ out.println("s.src = \"//an.yandex.ru/system/context.js\";");
+ out.println("s.async = true");
+ out.println("t.parentNode.insertBefore(s, t);");
+ out.println("})(window, document, \"yandex_context_callbacks\");");
+ out.println("} else { $('#yandex_ad_728').remove(); }");
+ out.println("</script>");
+ */
+ out.println("<script async src='//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js'></script>");
+ out.println("<ins class='adsbygoogle' data-ad-client='ca-pub-2439005094311303' data-ad-slot='2131891641'></ins>");
+ out.println("<script>");
+ out.println("(adsbygoogle = window.adsbygoogle || []).push({})");
+ out.println("</script>");
+ }
+
+ public static void pageHomeColumn(PrintWriter out, Connection sql, com.juick.User visitor) {
+ pageHomeColumn(out, sql, visitor, false);
+ }
+
+ public static void pageHomeColumn(PrintWriter out, Connection sql, com.juick.User visitor, boolean showAdv) {
+ if (tagsHTML == null) {
+ tagsHTML = PageTemplates.getPopularTags(sql, 80);
+ }
+
+ out.println("<aside id=\"column\">");
+ out.print(" <p class=\"tags\">" + tagsHTML);
+ if (showAdv) {
+ out.print(" <a href=\"http://ru.wix.com/\">конструктор сайтов</a>");
+ }
+ out.println("</p>");
+// if (visitor != null) {
+// printContestRating(out, sql);
+// }
+ out.println("</aside>");
+ }
+
+ public static String getPopularTags(Connection sql, int cnt) {
+ String ret = "";
+
+ PreparedStatement stmt = null;
+ ResultSet rs = null;
+ try {
+ stmt = sql.prepareStatement("SELECT name FROM tags WHERE top=1 ORDER BY name ASC");
+ rs = stmt.executeQuery();
+ rs.beforeFirst();
+ while (rs.next()) {
+ if (!ret.isEmpty()) {
+ ret += " ";
+ }
+ try {
+ ret += "<a href=\"/tag/" + URLEncoder.encode(rs.getString(1), "UTF-8") + "\">" + Utils.encodeHTML(rs.getString(1)) + "</a>";
+ } catch (UnsupportedEncodingException e) {
+ }
+
+ }
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(rs, stmt);
+ }
+
+ return ret;
+ }
+
+ public static void printContestRating(PrintWriter out, Connection sql) {
+ out.println("<hr/>");
+ out.println("<!--noindex-->");
+ out.println("<p style=\"font-size: 14pt\">Кто <a href=\"/help/ru/contest\">выиграет iPod</a>?</p>");
+ out.println("<table width=\"100%\">");
+
+ int i = 0;
+ PreparedStatement stmt = null;
+ ResultSet rs = null;
+ try {
+ stmt = sql.prepareStatement("SELECT users.id,users.nick,COUNT(users_refs.user_id) AS cnt FROM users INNER JOIN users_refs ON users.id=users_refs.ref WHERE users.id>2 GROUP BY users_refs.ref ORDER BY cnt DESC LIMIT 10");
+ rs = stmt.executeQuery();
+ rs.beforeFirst();
+ while (rs.next()) {
+ String uname = rs.getString(2);
+ if (i == 0) {
+ out.println(" <tr><td><b><a href=\"/" + uname + "/\">" + uname + "</a></b></td><td align=\"right\"><b>" + rs.getInt(3) + "</b></td></tr>");
+ } else {
+ out.println(" <tr><td><a href=\"/" + uname + "/\">" + uname + "</a></td><td align=\"right\">" + rs.getInt(3) + "</td></tr>");
+ }
+ i++;
+ }
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(rs, stmt);
+ }
+
+ out.println("</table>");
+ out.println("<!--/noindex-->");
+ }
+
+ public static void pageFooter(HttpServletRequest request, PrintWriter out, com.juick.User visitor, boolean sapeon) {
+ out.println("<div id=\"footer\">");
+ out.println(" <div id=\"footer-right\"><a href=\"/settings\" rel=\"nofollow\">Настройки</a> &#183; <a href=\"/help/ru/contacts\" rel=\"nofollow\">Контакты</a> &#183; <a href=\"/help/\" rel=\"nofollow\">Справка</a> &#183; <a href=\"/help/ru/adv\" rel=\"nofollow\">Реклама</a></div>");
+ out.print(" <div id=\"footer-social\">");
+ out.print("<a href=\"https://twitter.com/Juick\" rel=\"nofollow\" class=\"ico32-twi\">Twitter</a>");
+ out.print("<a href=\"https://vk.com/juick\" rel=\"nofollow\" class=\"ico32-vk\">ВКонтакте</a>");
+ out.print("<a href=\"https://www.facebook.com/JuickCom\" rel=\"nofollow\" class=\"ico32-fb\">Facebook</a>");
+ out.println("</div>");
+ out.print(" <div id=\"footer-left\">juick.com &copy; 2008-2014");
+
+ String queryString = request.getQueryString();
+ String requestURI = request.getRequestURI();
+ if (sapeon && sape != null && (visitor == null || visitor.UID == 1) && queryString == null) {
+ String links = sape.getPageLinks(requestURI, request.getCookies()).render();
+ if (links != null && !links.isEmpty()) {
+ out.print("<br/>Спонсоры: " + links);
+ }
+ }
+ if ((visitor == null || visitor.UID == 1) && queryString != null && requestURI != null && requestURI.equals("/")) {
+ out.print("<br/><a href=\"http://siam.li/\">сиам</a>");
+ }
+
+ out.println("</div>");
+ out.println("</div>");
+
+ if (visitor != null) {
+ out.println("<script type=\"text/javascript\">");
+ out.println("var hash=\"" + visitor.AuthHash + "\";");
+ out.println("</script>");
+ }
+
+ out.println("<script>");
+ out.println("(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){");
+ out.println("(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),");
+ out.println("m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)");
+ out.println("})(window,document,'script','//www.google-analytics.com/analytics.js','ga');");
+ out.println("ga('create','UA-385578-4','juick.com');");
+ out.println("ga('require','displayfeatures');");
+ out.println("ga('send','pageview');");
+
+ if (sapeon) {
+ out.println("var _acic={dataProvider:10};");
+ out.println("(function(){");
+ out.println("var e=document.createElement('script');e.type='text/javascript';e.async=true;e.src='//www2.aci'+'nt.net/aci.js';");
+ out.println("var t=document.getElementsByTagName('script')[0];t.parentNode.insertBefore(e,t);");
+ out.println("})();");
+ }
+
+ out.println("</script>");
+ }
+
+ public static void pageEnd(PrintWriter out) {
+ out.println("</body></html>");
+ }
+
+ public static String formatTags(ArrayList<com.juick.Tag> tags) {
+ String ret = "";
+ for (int i = 0; i < tags.size(); i++) {
+ com.juick.Tag tag = tags.get(i);
+ String tagName = tag.Name.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
+ try {
+ ret += " *<a href=\"/tag/" + URLEncoder.encode(tag.Name, "utf-8") + "\"";
+ if (tag.UsageCnt < 2) {
+ ret += " rel=\"nofollow\"";
+ }
+ ret += ">" + tagName + "</a>";
+ } catch (UnsupportedEncodingException e) {
+ }
+ }
+
+ return ret;
+ }
+
+ public static String formatTags(ArrayList<String> tags, com.juick.User user) {
+ String ret = "";
+ for (int i = 0; i < tags.size(); i++) {
+ String tag = tags.get(i);
+ tag = tag.replaceAll("<", "&lt;");
+ tag = tag.replaceAll(">", "&gt;");
+ try {
+ ret += " *<a href=\"";
+ if (user == null) {
+ ret += "/tag/";
+ } else {
+ ret += "/" + user.UName + "/?tag=";
+ }
+ ret += URLEncoder.encode(tags.get(i), "utf-8") + "\">" + tag + "</a>";
+ } catch (UnsupportedEncodingException e) {
+ }
+ }
+
+ return ret;
+ }
+
+ public static String formatDate(int minutes, String fulldate) {
+ if (minutes < 1) {
+ return "сейчас";
+ } else if (minutes < 60) {
+ String unit;
+ int ld = minutes % 10;
+ if ((minutes < 10 || minutes > 20) && ld == 1) {
+ unit = "минуту";
+ } else if ((minutes < 10 || minutes > 20) && ld > 1 && ld < 5) {
+ unit = "минуты";
+ } else {
+ unit = "минут";
+ }
+ return minutes + " " + unit + " назад";
+ } else if (minutes < 1440) {
+ int hours = (minutes / 60);
+ String unit;
+ int ld = hours % 10;
+ if ((hours < 10 || hours > 20) && ld == 1) {
+ unit = "час";
+ } else if ((hours < 10 || hours > 20) && ld > 1 && ld < 5) {
+ unit = "часа";
+ } else {
+ unit = "часов";
+ }
+ return hours + " " + unit + " назад";
+ } else if (minutes < 20160) {
+ int days = (minutes / 1440);
+ String unit;
+ int ld = days % 10;
+ if ((days < 10 || days > 20) && ld == 1) {
+ unit = "день";
+ } else if ((days < 10 || days > 20) && ld > 1 && ld < 5) {
+ unit = "дня";
+ } else {
+ unit = "дней";
+ }
+ return days + " " + unit + " назад";
+ } else {
+ String ret = fulldate;
+ synchronized (sdfSQL) {
+ try {
+ Date pDate = sdfSQL.parse(fulldate);
+ Calendar c = Calendar.getInstance();
+ int curyear = c.get(Calendar.YEAR);
+ c.setTime(pDate);
+ if (c.get(Calendar.YEAR) == curyear) {
+ ret = sdfSimple.format(pDate);
+ } else {
+ ret = sdfFull.format(pDate);
+ }
+ } catch (Exception e) {
+ System.err.println("PARSE EXCEPTION: " + fulldate);
+ }
+ }
+ return ret;
+ }
+ }
+
+ public static String formatJSLocalTime(String ts) {
+ String ret = "";
+ synchronized (sdfSQL) {
+ try {
+ Date date = sdfSQL.parse(ts);
+ ret = "<script type=\"text/javascript\">"
+ + "var d=new Date(" + date.getTime() + ");"
+ + "document.write((d.getDate()<10?'0':'')+d.getDate()+'.'+(d.getMonth()<9?'0':'')+(d.getMonth()+1)+'.'+d.getFullYear()+' '+(d.getHours()<10?'0':'')+d.getHours()+':'+(d.getMinutes()<10?'0':'')+d.getMinutes());"
+ + "</script>";
+ } catch (Exception e) {
+ System.err.println("PARSE EXCEPTION: " + ts);
+ }
+ }
+ return ret;
+ }
+
+ public static String formatReplies(int replies) {
+ int ld = replies % 10;
+ int lh = replies % 100;
+ if ((lh < 10 || lh > 20) && ld == 1) {
+ return replies + " ответ";
+ } else if ((lh < 10 || lh > 20) && ld > 1 && ld < 5) {
+ return replies + " ответа";
+ } else {
+ return replies + " ответов";
+ }
+ }
+ private static Pattern regexLinks2 = Pattern.compile("((?<=\\s)|(?<=\\A))([\\[\\{]|&lt;)((?:ht|f)tps?://(?:www\\.)?([^\\/\\s\\\"\\)\\!]+)/?(?:[^\\]\\}](?<!&gt;))*)([\\]\\}]|&gt;)");
+
+ public static String formatMessageCode(String msg) {
+ msg = msg.replaceAll("&", "&amp;");
+ msg = msg.replaceAll("<", "&lt;");
+ msg = msg.replaceAll(">", "&gt;");
+
+ // http://juick.com/last?page=2
+ // <a href="http://juick.com/last?page=2" rel="nofollow">http://juick.com/last?page=2</a>
+ msg = msg.replaceAll("((?<=\\s)|(?<=\\A))((?:ht|f)tps?://(?:www\\.)?([^\\/\\s\\n\\\"]+)/?[^\\s\\n\\\"]*)", "$1<a href=\"$2\" rel=\"nofollow\">$2</a>");
+
+ // (http://juick.com/last?page=2)
+ // (<a href="http://juick.com/last?page=2" rel="nofollow">http://juick.com/last?page=2</a>)
+ Matcher m = regexLinks2.matcher(msg);
+ StringBuffer sb = new StringBuffer();
+ while (m.find()) {
+ String url = m.group(3).replace(" ", "%20").replaceAll("\\s+", "");
+ m.appendReplacement(sb, "$1$2<a href=\"" + url + "\" rel=\"nofollow\">" + url + "</a>$5");
+ }
+ m.appendTail(sb);
+ msg = sb.toString();
+
+ return "<pre>" + msg + "</pre>";
+ }
+
+ public static String formatMessage(String msg) {
+ msg = msg.replaceAll("&", "&amp;");
+ msg = msg.replaceAll("<", "&lt;");
+ msg = msg.replaceAll(">", "&gt;");
+
+ // --
+ // &mdash;
+ msg = msg.replaceAll("((?<=\\s)|(?<=\\A))\\-\\-?((?=\\s)|(?=\\Z))", "$1&mdash;$2");
+
+ // http://juick.com/last?page=2
+ // <a href="http://juick.com/last?page=2" rel="nofollow">juick.com</a>
+ msg = msg.replaceAll("((?<=\\s)|(?<=\\A))((?:ht|f)tps?://(?:www\\.)?([^\\/\\s\\n\\\"]+)/?[^\\s\\n\\\"]*)", "$1<a href=\"$2\" rel=\"nofollow\">$3</a>");
+
+ // [link text][http://juick.com/last?page=2]
+ // <a href="http://juick.com/last?page=2" rel="nofollow">link text</a>
+ msg = msg.replaceAll("\\[([^\\]]+)\\]\\[((?:ht|f)tps?://[^\\]]+)\\]", "<a href=\"$2\" rel=\"nofollow\">$1</a>");
+ msg = msg.replaceAll("\\[([^\\]]+)\\]\\(((?:ht|f)tps?://[^\\)]+)\\)", "<a href=\"$2\" rel=\"nofollow\">$1</a>");
+
+ // #12345
+ // <a href="http://juick.com/12345">#12345</a>
+ msg = msg.replaceAll("((?<=\\s)|(?<=\\A)|(?<=\\p{Punct}))#(\\d+)((?=\\s)|(?=\\Z)|(?=\\))|(?=\\.)|(?=\\,))", "$1<a href=\"http://juick.com/$2\">#$2</a>$3");
+
+ // #12345/65
+ // <a href="http://juick.com/12345#65">#12345/65</a>
+ msg = msg.replaceAll("((?<=\\s)|(?<=\\A)|(?<=\\p{Punct}))#(\\d+)/(\\d+)((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1<a href=\"http://juick.com/$2#$3\">#$2/$3</a>$4");
+
+ // *bold*
+ // <b>bold</b>
+ msg = msg.replaceAll("((?<=\\s)|(?<=\\A)|(?<=\\p{Punct}))\\*([^\\*\\n<>]+)\\*((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1<b>$2</b>$3");
+
+ // /italic/
+ // <i>italic</i>
+ msg = msg.replaceAll("((?<=\\s)|(?<=\\A))/([^\\/\\n<>]+)/((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1<i>$2</i>$3");
+
+ // _underline_
+ // <span class="u">underline</span>
+ msg = msg.replaceAll("((?<=\\s)|(?<=\\A))_([^\\_\\n<>]+)_((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1<span class=\"u\">$2</span>$3");
+
+ // /12
+ // <a href="#12">/12</a>
+ msg = msg.replaceAll("((?<=\\s)|(?<=\\A))\\/(\\d+)((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1<a href=\"#$2\">/$2</a>$3");
+
+ // @username@jabber.org
+ // <a href="http://juick.com/username@jabber.org/">@username@jabber.org</a>
+ msg = msg.replaceAll("((?<=\\s)|(?<=\\A))@([\\w\\-\\.]+@[\\w\\-\\.]+)((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1<a href=\"http://juick.com/$2/\">@$2</a>$3");
+
+ // @username
+ // <a href="http://juick.com/username/">@username</a>
+ msg = msg.replaceAll("((?<=\\s)|(?<=\\A))@([\\w\\-]{2,16})((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1<a href=\"http://juick.com/$2/\">@$2</a>$3");
+
+ // (http://juick.com/last?page=2)
+ // (<a href="http://juick.com/last?page=2" rel="nofollow">juick.com</a>)
+ Matcher m = regexLinks2.matcher(msg);
+ StringBuffer sb = new StringBuffer();
+ while (m.find()) {
+ String url = m.group(3).replace(" ", "%20").replaceAll("\\s+", "");
+ m.appendReplacement(sb, "$1$2<a href=\"" + url + "\" rel=\"nofollow\">$4</a>$5");
+ }
+ m.appendTail(sb);
+ msg = sb.toString();
+
+ // > citate
+ msg = msg.replaceAll("(?:(?<=\\n)|(?<=\\A))&gt; *(.*)?(\\n|(?=\\Z))", "<blockquote>$1</blockquote>");
+ msg = msg.replaceAll("</blockquote><blockquote>", "\n");
+
+ msg = msg.replaceAll("\n", "<br/>\n");
+ return msg;
+ }
+
+ public static void printMessages(PrintWriter out, Connection sql, com.juick.User user, ArrayList<Integer> mids, com.juick.User visitor, int YandexID, int ad_mid) {
+ ArrayList<com.juick.Message> msgs = MessagesQueries.getMessages(sql, mids);
+
+ for (int i = 0; i < msgs.size(); i++) {
+ com.juick.Message msg = msgs.get(i);
+ if (msg.MID == ad_mid) {
+ msgs.remove(i);
+ msgs.add(0, msg);
+ break;
+ }
+ }
+
+ ArrayList<Integer> blUIDs = new ArrayList<Integer>(20);
+ if (visitor != null) {
+ for (int i = 0; i < msgs.size(); i++) {
+ blUIDs.add(msgs.get(i).User.UID);
+ }
+ blUIDs = UserQueries.checkBL(sql, visitor.UID, blUIDs);
+ }
+
+ for (int i = 0; i < msgs.size(); i++) {
+
+ if (i == 0 && YandexID > 0 && ad_mid == 0) {
+ pageYandexAd728(out, YandexID);
+ }
+
+ com.juick.Message msg = msgs.get(i);
+
+ ArrayList<com.juick.Tag> tags = MessagesQueries.getMessageTags(sql, msg.MID);
+ String tagsStr = formatTags(tags);
+ if (msg.ReadOnly) {
+ tagsStr += " *readonly";
+ }
+ if (msg.Privacy < 0) {
+ tagsStr += " *friends";
+ }
+ if (msg.MID == ad_mid) {
+ tagsStr += " *реклама";
+ }
+
+ String txt;
+ if (!msg.Tags.isEmpty() && msg.Tags.contains("code")) {
+ txt = formatMessageCode(msg.Text);
+ } else {
+ txt = formatMessage(msg.Text);
+ }
+
+ out.println("<article data-mid=\"" + msg.MID + "\">");
+ out.println(" <aside><a href=\"/" + msg.User.UName + "/\"><img src=\"//i.juick.com/a/" + msg.User.UID + ".png\" alt=\"" + msg.User.UName + "\"/></a></aside>");
+ out.println(" <header class=\"u\">@<a href=\"/" + msg.User.UName + "/\">" + msg.User.UName + "</a>:" + tagsStr + "</header>");
+ out.println(" <header class=\"t\"><a href=\"/" + msg.User.UName + "/" + msg.MID + "\"><time datetime=\"" + msg.TimestampString + "Z\" title=\"" + msg.TimestampString + " GMT\">" + formatDate(msg.TimeAgo, msg.TimestampString) + "</time></a></header>");
+ if (msg.AttachmentType != null) {
+ String fname = msg.MID + "." + msg.AttachmentType;
+ out.println(" <p class=\"ir\"><a href=\"//i.juick.com/photos-512/" + fname + "\" onclick=\"return showPhotoDialog('" + fname + "')\"><img src=\"//i.juick.com/photos-512/" + fname + "\" alt=\"\"/></a></p>");
+ }
+ out.println(" <p>" + txt + "</p>");
+ if (msg.AttachmentType != null) {
+ out.println(" <div class=\"irbr\"></div>");
+ }
+ out.print(" <nav class=\"l\">");
+ msg.ReadOnly |= blUIDs.contains(msg.User.UID);
+ if (visitor == null && msg.ReadOnly == false) {
+ out.print("<a href=\"#\" onclick=\"return openDialogLogin()\">Комментировать</a> ");
+ } else if (visitor != null && (msg.ReadOnly == false || visitor.UID == msg.User.UID)) {
+ out.print("<a href=\"#\" onclick=\"return showCommentFooter(this)\">Комментировать</a> ");
+ }
+ out.print("<a href=\"#\" onclick=\"return likeMessage(this," + msg.MID + ")\">Мне нравится</a>");
+ if (visitor != null && msg.Privacy < 0 && msg.User.UID == visitor.UID) {
+ out.print(" <a href=\"#\" onclick=\"return setPrivacy(this," + msg.MID + ")\">Открыть доступ</a>");
+ }
+ if (visitor != null && visitor.UID == 1) {
+ out.print(" <a href=\"#\" onclick=\"return setPopular(this," + msg.MID + ",2)\">+</a>");
+ out.print(" <a href=\"#\" onclick=\"return setPopular(this," + msg.MID + ",-1)\">-</a>");
+ out.print(" <a href=\"#\" onclick=\"return setPopular(this," + msg.MID + ",-2)\">x</a>");
+ }
+ out.println("</nav>");
+
+ out.print(" <nav class=\"s\">");
+ if (msg.Likes > 0) {
+ out.print("<a href=\"/" + msg.User.UName + "/" + msg.MID + "\" class=\"likes\">" + msg.Likes + "</a>");
+ }
+ if (msg.Replies > 0) {
+ out.print("<a href=\"/" + msg.User.UName + "/" + msg.MID + "\" class=\"replies\">" + msg.Replies + "</a>");
+ }
+ out.println("</nav>");
+ out.print("</article>");
+ }
+ }
+}
diff --git a/src/main/java/com/juick/http/www/RSS.java b/src/main/java/com/juick/http/www/RSS.java
new file mode 100644
index 00000000..ab96221e
--- /dev/null
+++ b/src/main/java/com/juick/http/www/RSS.java
@@ -0,0 +1,113 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2013, ugnich
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick.http.www;
+
+import com.juick.Message;
+import com.juick.server.MessagesQueries;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ *
+ * @author ugnich
+ */
+public class RSS {
+
+ private static final SimpleDateFormat sdfSQL = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ private static final SimpleDateFormat sdfRSS = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z");
+
+ protected void doGet(Connection sql, HttpServletRequest request, HttpServletResponse response, int uid, String uname) throws ServletException, IOException {
+ ArrayList<Integer> mids = MessagesQueries.getUserBlog(sql, uid, 0, 0);
+ if (mids.isEmpty()) {
+ response.sendError(404);
+ return;
+ }
+
+ ArrayList<Message> msgs = MessagesQueries.getMessages(sql, mids);
+
+ response.setContentType("application/rss+xml; charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ try {
+ out.println("<?xml version='1.0' encoding='utf-8'?>");
+ out.println("<rss version='2.0' xmlns:atom='http://www.w3.org/2005/Atom' xmlns:slash='http://purl.org/rss/1.0/modules/slash/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:media='http://search.yahoo.com/mrss/' xmlns:juick='http://juick.com/'>");
+ out.println("<channel>");
+ out.println("<atom:link href='http://rss.juick.com/" + uname + "/blog' rel='self' type='application/rss+xml'/>");
+ out.println("<title>" + uname + " - Juick</title>");
+ out.println("<link>http://juick.com/" + uname + "/</link>");
+ out.println("<description>The latest messages by @" + uname + " at Juick</description>");
+ out.println("<image><url>http://i.juick.com/a/" + uid + ".png</url><title>" + uname + " - Juick</title><link>http://juick.com/" + uname + "/</link></image>");
+
+ Iterator<Message> i = msgs.iterator();
+ while (i.hasNext()) {
+ Message msg = i.next();
+
+ out.println("<item>");
+ out.println("<link>http://juick.com/" + msg.User.UName + "/" + msg.MID + "</link>");
+ out.println("<guid>http://juick.com/" + msg.User.UName + "/" + msg.MID + "</guid>");
+
+ out.print("<title><![CDATA[@" + msg.User.UName + ":");
+ if (!msg.Tags.isEmpty()) {
+ for (int n = 0; n < msg.Tags.size(); n++) {
+ out.print(" *" + msg.Tags.get(n));
+ }
+ }
+ out.println("]]></title>");
+ out.println("<description><![CDATA[" + PageTemplates.formatMessage(msg.Text) + "]]></description>");
+
+ synchronized (sdfSQL) {
+ try {
+ Date date = sdfSQL.parse(msg.TimestampString);
+ out.println("<pubDate>" + sdfRSS.format(date) + "</pubDate>");
+ } catch (Exception e) {
+ System.err.println("PARSE EXCEPTION: " + msg.TimestampString);
+ }
+ }
+
+ out.println("<comments>http://juick.com/" + msg.User.UName + "/" + msg.MID + "</comments>");
+ if (!msg.Tags.isEmpty()) {
+ for (int n = 0; n < msg.Tags.size(); n++) {
+ out.println("<category>" + msg.Tags.get(n) + "</category>");
+ }
+ }
+ if (msg.AttachmentType != null) {
+ if (msg.AttachmentType.equals("jpg")) {
+ out.println("<media:content url='http://i.juick.com/photos-1024/" + msg.MID + ".jpg' type='image/jpeg'/>");
+ out.println("<media:thumbnail url='http://i.juick.com/ps/" + msg.MID + ".jpg'/>");
+ } else if (msg.AttachmentType.equals("png")) {
+ out.println("<media:content url='http://i.juick.com/photos-1024/" + msg.MID + ".png' type='image/png'/>");
+ out.println("<media:thumbnail url='http://i.juick.com/ps/" + msg.MID + ".png'/>");
+ }
+ }
+ out.println("<juick:user uid='" + msg.User.UID + "'/>");
+ out.println("</item>");
+ }
+
+ out.println("</channel></rss>");
+ } finally {
+ out.close();
+ }
+ }
+}
diff --git a/src/main/java/com/juick/http/www/Settings.java b/src/main/java/com/juick/http/www/Settings.java
new file mode 100644
index 00000000..de37bdd0
--- /dev/null
+++ b/src/main/java/com/juick/http/www/Settings.java
@@ -0,0 +1,104 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2013, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick.http.www;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class Settings {
+
+ protected void doGet(Connection sql, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+
+ response.setContentType("text/html; charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ try {
+ PageTemplates.pageHead(out, "Логин", "");
+ PageTemplates.pageNavigation(out, visitor, null);
+
+ out.println("<div id=\"topwrapper\">");
+ out.println("<div id=\"wrapper\">");
+ out.println("<div id=\"content\">");
+ out.println("<form action=\"/login\" method=\"post\">");
+ out.println("<p>Имя пользователя: <input type=\"text\" name=\"username\"/></p>");
+ out.println("<p>Пароль: <input type=\"password\" name=\"password\"/></p>");
+ out.println("<p><input type=\"submit\" value=\" OK \"/></p>");
+ out.println("</form>");
+ out.println("</div>");
+ out.println("</div>");
+ out.println("</div>"); // topwrapper
+
+ PageTemplates.pageFooter(request, out, visitor, false);
+ PageTemplates.pageEnd(out);
+ } finally {
+ out.close();
+ }
+ }
+
+ protected void doPost(Connection sql, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ String username = request.getParameter("username");
+ String password = request.getParameter("password");
+ if (username == null || password == null || username.length() > 32 || password.isEmpty()) {
+ response.sendError(400);
+ return;
+ }
+
+ int uid = com.juick.server.UserQueries.checkPassword(sql, username, password);
+ if (uid > 0) {
+ String hash = com.juick.server.UserQueries.getHashByUID(sql, uid);
+ Cookie c = new Cookie("hash", hash);
+ c.setDomain(".juick.com");
+ c.setMaxAge(365 * 24 * 60 * 60);
+ response.addCookie(c);
+
+
+ if (uid > 0) {
+ PreparedStatement stmt = null;
+ try {
+ stmt = sql.prepareStatement("DELETE FROM logins WHERE user_id=?");
+ stmt.setInt(1, uid);
+ stmt.executeUpdate();
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(null, stmt);
+ }
+ }
+
+ String referer = request.getHeader("Referer");
+ if (referer != null && referer.startsWith("http://juick.com/") && !referer.equals("http://juick.com/login")) {
+ response.sendRedirect(referer);
+ } else {
+ response.sendRedirect("/");
+ }
+ } else {
+ response.sendError(403);
+ }
+ }
+}
diff --git a/src/main/java/com/juick/http/www/SignUp.java b/src/main/java/com/juick/http/www/SignUp.java
new file mode 100644
index 00000000..395232a6
--- /dev/null
+++ b/src/main/java/com/juick/http/www/SignUp.java
@@ -0,0 +1,331 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2013, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick.http.www;
+
+import com.juick.server.UserQueries;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class SignUp {
+
+ protected void doGet(Connection sql, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+
+ String type = request.getParameter("type");
+ String hash = request.getParameter("hash");
+ if (type == null || type.isEmpty() || hash == null || hash.isEmpty() || hash.length() > 36 || !type.matches("^[a-zA-Z0-9\\-]+$") || !hash.matches("^[a-zA-Z0-9\\-]+$")) {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ String account = null;
+ if (type.equals("fb")) {
+ account = getFacebookNameByHash(sql, hash);
+ } else if (type.equals("vk")) {
+ account = getVKNameByHash(sql, hash);
+ } else if (type.equals("xmpp")) {
+ account = getJIDByHash(sql, hash);
+ }
+ if (account == null) {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ response.setContentType("text/html; charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ try {
+ PageTemplates.pageHead(out, "Новый пользователь", null);
+ PageTemplates.pageNavigation(out, visitor, null);
+
+ out.println("<section id=\"content\">");
+
+ out.print("<h1 class=\"signup-h1\">");
+ if (type.charAt(0) == 'f') {
+ out.print("<img src=\"//static.juick.com/settings/facebook.png\" alt=\"Facebook\"/>");
+ } else if (type.charAt(0) == 'v') {
+ out.print("<img src=\"//static.juick.com/settings/vk.png\" alt=\"VKontakte\"/>");
+ } else if (type.charAt(0) == 'x') {
+ out.print("<img src=\"//static.juick.com/settings/xmpp.png\" alt=\"XMPP\"/>");
+ }
+ out.println(account + "</h1>");
+
+ out.println("<h2 class=\"signup-h2\">Связать с существующим аккаунтом Juick</h2>");
+ out.println("<form action=\"/signup\" method=\"post\">");
+ out.println("<input type=\"hidden\" name=\"action\" value=\"link\"/>");
+ out.println("<input type=\"hidden\" name=\"type\" value=\"" + type + "\"/>");
+ out.println("<input type=\"hidden\" name=\"hash\" value=\"" + hash + "\"/>");
+ if (visitor != null) {
+ out.println("<input type=\"submit\" value=\"Связать с этим аккаунтом\"/>");
+ } else {
+ out.println("<p>Имя пользователя: <input type=\"text\" name=\"username\"/></p>");
+ out.println("<p>Пароль: <input type=\"password\" name=\"password\"/></p>");
+ out.println("<p><input type=\"submit\" value=\" OK \"/></p>");
+ }
+ out.println("</form>");
+
+ out.println("<hr class=\"signup-hr\"/>");
+
+ out.println("<h2 class=\"signup-h2\">Создать новый аккаунт Juick</h2>");
+ out.println("<form action=\"/signup\" method=\"post\">");
+ out.println("<input type=\"hidden\" name=\"action\" value=\"new\"/>");
+ out.println("<input type=\"hidden\" name=\"type\" value=\"" + type + "\"/>");
+ out.println("<input type=\"hidden\" name=\"hash\" value=\"" + hash + "\"/>");
+ out.println("<p>Имя пользователя: <input type=\"text\" name=\"username\" id=\"username\" onblur=\"checkUsername()\"/><br/><i>(От 2-х до 16-и латинских символов и/или цифр, дефис)</i></p>");
+ out.println("<p>Пароль: <input type=\"password\" name=\"password\"/><br/><i>(от 6-и до 32-х символов)</i></p>");
+ out.println("<p><input type=\"submit\" value=\" OK \"/></p>");
+ out.println("</form>");
+
+ out.println("</section>");
+
+ PageTemplates.pageFooter(request, out, visitor, false);
+ PageTemplates.pageEnd(out);
+ } finally {
+ out.close();
+ }
+ }
+
+ protected void doPost(Connection sql, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+ int uid = 0;
+
+ String type = request.getParameter("type");
+ String hash = request.getParameter("hash");
+ if (type == null || type.isEmpty() || hash == null || hash.isEmpty() || hash.length() > 36 || !type.matches("^[a-zA-Z0-9\\-]+$") || !hash.matches("^[a-zA-Z0-9\\-]+$")) {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ String action = request.getParameter("action");
+ if (action.charAt(0) == 'l') {
+
+ if (visitor == null) {
+ String username = request.getParameter("username");
+ String password = request.getParameter("password");
+ if (username == null || password == null || username.length() > 32 || password.isEmpty()) {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+ uid = com.juick.server.UserQueries.checkPassword(sql, username, password);
+ } else {
+ uid = visitor.UID;
+ }
+
+ if (uid <= 0) {
+ response.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ if (!(type.charAt(0) == 'f' && setFacebookUser(sql, hash, uid))
+ && !(type.charAt(0) == 'v' && setVKUser(sql, hash, uid))
+ && !(type.charAt(0) == 'x' && setJIDUser(sql, hash, uid))) {
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ } else { // Create new account
+ String username = request.getParameter("username");
+ String password = request.getParameter("password");
+ if (username == null || password == null || username.length() < 2 || username.length() > 16 || !username.matches("^[a-zA-Z0-9\\-]+$") || password.length() < 6 || password.length() > 32) {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ // CHECK USERNAME
+
+ uid = UserQueries.createUser(sql, username, password);
+ if (uid <= 0) {
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ if (!(type.charAt(0) == 'f' && setFacebookUser(sql, hash, uid))
+ && !(type.charAt(0) == 'v' && setVKUser(sql, hash, uid))
+ && !(type.charAt(0) == 'x' && setJIDUser(sql, hash, uid))) {
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ int ref = 0;
+ String sRef = Utils.getCookie(request, "ref");
+ if (sRef != null) {
+ try {
+ ref = Integer.parseInt(sRef);
+ } catch (Exception e) {
+ }
+ }
+
+ if (ref > 0) {
+ setUserRef(sql, uid, ref);
+ }
+
+ visitor = null;
+ }
+
+ if (visitor == null) {
+ hash = com.juick.server.UserQueries.getHashByUID(sql, uid);
+ Cookie c = new Cookie("hash", hash);
+ c.setMaxAge(365 * 24 * 60 * 60);
+ response.addCookie(c);
+ }
+
+ response.sendRedirect("/");
+ }
+
+ private boolean setUserRef(Connection sql, int uid, int ref) {
+ boolean ret = false;
+ PreparedStatement stmt = null;
+ try {
+ stmt = sql.prepareStatement("INSERT INTO users_refs(user_id,ref) VALUES (?,?)");
+ stmt.setInt(1, uid);
+ stmt.setInt(2, ref);
+ stmt.executeUpdate();
+ ret = true;
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(null, stmt);
+ }
+ return ret;
+ }
+
+ private String getFacebookNameByHash(Connection sql, String hash) {
+ String ret = null;
+
+ PreparedStatement stmt = null;
+ ResultSet rs = null;
+ try {
+ stmt = sql.prepareStatement("SELECT fb_name,fb_link FROM facebook WHERE loginhash=?");
+ stmt.setString(1, hash);
+ rs = stmt.executeQuery();
+ if (rs.first()) {
+ ret = "<a href=\"" + rs.getString(2) + "\" rel=\"nofollow\">" + rs.getString(1) + "</a>";
+ }
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(rs, stmt);
+ }
+
+ return ret;
+ }
+
+ private boolean setFacebookUser(Connection sql, String hash, int uid) {
+ boolean ret = false;
+ PreparedStatement stmt = null;
+ try {
+ stmt = sql.prepareStatement("UPDATE facebook SET user_id=?,loginhash=NULL WHERE loginhash=?");
+ stmt.setInt(1, uid);
+ stmt.setString(2, hash);
+ stmt.executeUpdate();
+ ret = true;
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(null, stmt);
+ }
+ return ret;
+ }
+
+ private String getVKNameByHash(Connection sql, String hash) {
+ String ret = null;
+
+ PreparedStatement stmt = null;
+ ResultSet rs = null;
+ try {
+ stmt = sql.prepareStatement("SELECT vk_name,vk_link FROM vk WHERE loginhash=?");
+ stmt.setString(1, hash);
+ rs = stmt.executeQuery();
+ if (rs.first()) {
+ ret = "<a href=\"http://vk.com/" + rs.getString(2) + "\" rel=\"nofollow\">" + rs.getString(1) + "</a>";
+ }
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(rs, stmt);
+ }
+
+ return ret;
+ }
+
+ private boolean setVKUser(Connection sql, String hash, int uid) {
+ boolean ret = false;
+ PreparedStatement stmt = null;
+ try {
+ stmt = sql.prepareStatement("UPDATE vk SET user_id=?,loginhash=NULL WHERE loginhash=?");
+ stmt.setInt(1, uid);
+ stmt.setString(2, hash);
+ stmt.executeUpdate();
+ ret = true;
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(null, stmt);
+ }
+ return ret;
+ }
+
+ private String getJIDByHash(Connection sql, String hash) {
+ String ret = null;
+
+ PreparedStatement stmt = null;
+ ResultSet rs = null;
+ try {
+ stmt = sql.prepareStatement("SELECT jid FROM jids WHERE loginhash=?");
+ stmt.setString(1, hash);
+ rs = stmt.executeQuery();
+ if (rs.first()) {
+ ret = rs.getString(1);
+ }
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(rs, stmt);
+ }
+
+ return ret;
+ }
+
+ private boolean setJIDUser(Connection sql, String hash, int uid) {
+ boolean ret = false;
+ PreparedStatement stmt = null;
+ try {
+ stmt = sql.prepareStatement("UPDATE jids SET user_id=?,loginhash=NULL WHERE loginhash=?");
+ stmt.setInt(1, uid);
+ stmt.setString(2, hash);
+ stmt.executeUpdate();
+ ret = true;
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(null, stmt);
+ }
+ return ret;
+ }
+}
diff --git a/src/main/java/com/juick/http/www/User.java b/src/main/java/com/juick/http/www/User.java
new file mode 100644
index 00000000..83601ade
--- /dev/null
+++ b/src/main/java/com/juick/http/www/User.java
@@ -0,0 +1,413 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2011, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick.http.www;
+
+import com.juick.server.MessagesQueries;
+import com.juick.server.TagQueries;
+import com.juick.server.UserQueries;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class User {
+
+ protected void doGetBlog(Connection sql, Connection sqlSearch, HttpServletRequest request, HttpServletResponse response, com.juick.User user) throws ServletException, IOException {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+
+ ArrayList<Integer> mids;
+
+ String paramShow = request.getParameter("show");
+
+ com.juick.Tag paramTag = null;
+ String paramTagStr = request.getParameter("tag");
+ if (paramTagStr != null) {
+ if (paramTagStr.length() < 64) {
+ paramTag = TagQueries.getTag(sql, paramTagStr, false);
+ }
+ if (paramTag == null) {
+ Errors.doGet404(sql, request, response);
+ return;
+ } else if (!paramTag.Name.equals(paramTagStr)) {
+ String url = "/" + user.UName + "/?tag=" + URLEncoder.encode(paramTag.Name, "UTF-8");
+ Utils.sendPermanentRedirect(response, url);
+ return;
+ }
+ }
+
+ int paramBefore = 0;
+ String paramBeforeStr = request.getParameter("before");
+ if (paramBeforeStr != null) {
+ try {
+ paramBefore = Integer.parseInt(paramBeforeStr);
+ } catch (NumberFormatException e) {
+ }
+ }
+
+ String paramSearch = request.getParameter("search");
+ if (paramSearch != null && paramSearch.length() > 64) {
+ paramSearch = null;
+ }
+
+ int privacy = 0;
+ if (visitor != null) {
+ if (user.UID == visitor.UID || visitor.UID == 1) {
+ privacy = -3;
+ } else if (UserQueries.isInWL(sql, user.UID, visitor.UID)) {
+ privacy = -2;
+ }
+ }
+
+ String title;
+ if (paramShow == null) {
+ if (paramTag != null) {
+ title = "Блог " + user.UName + ": *" + Utils.encodeHTML(paramTag.Name);
+ mids = MessagesQueries.getUserTag(sql, user.UID, paramTag.TID, privacy, paramBefore);
+ } else if (paramSearch != null) {
+ title = "Блог " + user.UName + ": " + Utils.encodeHTML(paramSearch);
+ mids = MessagesQueries.getUserSearch(sql, sqlSearch, user.UID, Utils.encodeSphinx(paramSearch), privacy, paramBefore);
+ } else {
+ title = "Блог " + user.UName;
+ mids = MessagesQueries.getUserBlog(sql, user.UID, privacy, paramBefore);
+ }
+ } else if (paramShow.equals("recomm")) {
+ title = "Рекомендации " + user.UName;
+ mids = MessagesQueries.getUserRecommendations(sql, user.UID, paramBefore);
+ } else if (paramShow.equals("photos")) {
+ title = "Фотографии " + user.UName;
+ mids = MessagesQueries.getUserPhotos(sql, user.UID, privacy, paramBefore);
+ } else {
+ Errors.doGet404(sql, request, response);
+ return;
+ }
+
+ if (visitor == null) {
+ pageUserRefCookie(request, response, user.UID);
+ }
+
+ response.setContentType("text/html; charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ try {
+ String head = "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"@" + user.UName + "\" href=\"//rss.juick.com/" + user.UName + "/blog\"/>";
+ if (paramTag != null && TagQueries.getTagNoIndex(sql, paramTag.TID)) {
+ head += "<meta name=\"robots\" content=\"noindex,nofollow\"/>";
+ } else if (paramBefore > 0 || paramShow != null) {
+ head += "<meta name=\"robots\" content=\"noindex\"/>";
+ }
+ PageTemplates.pageHead(out, title, head);
+ PageTemplates.pageNavigation(out, visitor, null);
+ pageUserColumn(out, sql, user, visitor);
+
+ if (mids.size() > 0) {
+ out.println("<section id=\"content\">");
+
+ if (paramTag != null) {
+ out.println("<p class=\"page\"><a href=\"/tag/" + URLEncoder.encode(paramTag.Name, "UTF-8") + "\">← Все записи с тегом <b>" + Utils.encodeHTML(paramTag.Name) + "</b></a></p>");
+ }
+
+ PageTemplates.printMessages(out, sql, user, mids, visitor, visitor == null ? 4 : 5, 0);
+
+ if (mids.size() >= 20) {
+ String nextpage = "?before=" + mids.get(mids.size() - 1);
+ if (paramShow != null) {
+ nextpage += "&amp;show=" + paramShow;
+ }
+ if (paramTag != null) {
+ nextpage += "&amp;tag=" + URLEncoder.encode(paramTag.Name, "UTF-8");
+ }
+ if (paramSearch != null) {
+ nextpage += "&amp;search=" + URLEncoder.encode(paramSearch, "UTF-8");
+ }
+ out.println("<p class=\"page\"><a href=\"" + nextpage + "\" rel=\"prev\">Читать дальше →</a></p>");
+ }
+
+ out.println("</section>");
+ }
+
+ PageTemplates.pageFooter(request, out, visitor, true);
+ PageTemplates.pageEnd(out);
+ } finally {
+ out.close();
+ }
+ }
+
+ protected void doGetTags(Connection sql, HttpServletRequest request, HttpServletResponse response, com.juick.User user) throws ServletException, IOException {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+
+ if (visitor == null) {
+ pageUserRefCookie(request, response, user.UID);
+ }
+
+ response.setContentType("text/html; charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ try {
+ String head = "<meta name=\"robots\" content=\"noindex,nofollow\"/>";
+ PageTemplates.pageHead(out, "Теги " + user.UName, head);
+ PageTemplates.pageNavigation(out, visitor, null);
+ pageUserColumn(out, sql, user, visitor);
+
+ out.println("<section id=\"content\">");
+ out.println("<p>" + pageUserTags(sql, user, visitor, 0) + "</p>");
+ out.println("</section>");
+
+ PageTemplates.pageFooter(request, out, visitor, false);
+ PageTemplates.pageEnd(out);
+ } finally {
+ out.close();
+ }
+ }
+
+ protected void doGetFriends(Connection sql, HttpServletRequest request, HttpServletResponse response, com.juick.User user) throws ServletException, IOException {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+
+ if (visitor == null) {
+ pageUserRefCookie(request, response, user.UID);
+ }
+
+ response.setContentType("text/html; charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ try {
+ String head = "<meta name=\"robots\" content=\"noindex\"/>";
+ PageTemplates.pageHead(out, "Подписки " + user.UName, head);
+ PageTemplates.pageNavigation(out, visitor, null);
+ pageUserColumn(out, sql, user, visitor);
+
+ out.println("<section id=\"content\">");
+ out.println("<table class=\"users\"><tr>");
+
+ PreparedStatement stmt = null;
+ ResultSet rs = null;
+ try {
+ stmt = sql.prepareStatement("SELECT users.id,users.nick FROM subscr_users INNER JOIN users ON subscr_users.user_id=users.id WHERE subscr_users.suser_id=? ORDER BY users.nick");
+ stmt.setInt(1, user.UID);
+ rs = stmt.executeQuery();
+ rs.beforeFirst();
+ int cnt = 0;
+ while (rs.next()) {
+ if (cnt % 3 == 0 && cnt > 0) {
+ out.print("</tr><tr>");
+ }
+ out.print("<td><a href=\"/" + rs.getString(2) + "/\"><img src=\"//i.juick.com/as/" + rs.getInt(1) + ".png\"/>" + rs.getString(2) + "</a></td>");
+ cnt++;
+ }
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(rs, stmt);
+ }
+
+ out.println("</tr></table>");
+ out.println("</section>");
+
+ PageTemplates.pageFooter(request, out, visitor, false);
+ PageTemplates.pageEnd(out);
+ } finally {
+ out.close();
+ }
+ }
+
+ protected void doGetReaders(Connection sql, HttpServletRequest request, HttpServletResponse response, com.juick.User user) throws ServletException, IOException {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+
+ if (visitor == null) {
+ pageUserRefCookie(request, response, user.UID);
+ }
+
+ response.setContentType("text/html; charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ try {
+ String head = "<meta name=\"robots\" content=\"noindex\"/>";
+ PageTemplates.pageHead(out, "Читатели " + user.UName, head);
+ PageTemplates.pageNavigation(out, visitor, null);
+ pageUserColumn(out, sql, user, visitor);
+
+ out.println("<section id=\"content\">");
+ out.println("<table class=\"users\"><tr>");
+
+ PreparedStatement stmt = null;
+ ResultSet rs = null;
+ try {
+ stmt = sql.prepareStatement("SELECT users.id,users.nick FROM subscr_users INNER JOIN users ON subscr_users.suser_id=users.id WHERE subscr_users.user_id=? ORDER BY users.nick");
+ stmt.setInt(1, user.UID);
+ rs = stmt.executeQuery();
+ rs.beforeFirst();
+ int cnt = 0;
+ while (rs.next()) {
+ if (cnt % 3 == 0 && cnt > 0) {
+ out.print("</tr><tr>");
+ }
+ out.print("<td><a href=\"/" + rs.getString(2) + "/\"><img src=\"//i.juick.com/as/" + rs.getInt(1) + ".png\"/>" + rs.getString(2) + "</a></td>");
+ cnt++;
+ }
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(rs, stmt);
+ }
+
+ out.println("</tr></table>");
+ out.println("</section>");
+
+ PageTemplates.pageFooter(request, out, visitor, false);
+ PageTemplates.pageEnd(out);
+ } finally {
+ out.close();
+ }
+ }
+
+ public static void pageUserRefCookie(HttpServletRequest request, HttpServletResponse response, int uid) {
+ String hReferer = request.getHeader("Referer");
+ String ref = Utils.getCookie(request, "ref");
+
+ if (ref == null && (hReferer == null || !(hReferer.startsWith("http://juick.com/") || hReferer.startsWith("https://juick.com/")))) {
+ Cookie c = new Cookie("ref", Integer.toString(uid));
+ c.setMaxAge(7 * 24 * 60 * 60);
+ c.setPath("/");
+ response.addCookie(c);
+ }
+ }
+
+ public static void pageUserColumn(PrintWriter out, Connection sql, com.juick.User user, com.juick.User visitor) {
+ out.println("<aside id=\"column\">");
+ out.println(" <div id=\"ctitle\"><a href=\"./\"><img src=\"//i.juick.com/as/" + user.UID + ".png\" alt=\"\"/>" + user.UName + "</a></div>");
+ if (visitor != null && visitor.UID > 0 && visitor.UID != user.UID) {
+ out.println(" <ul id=\"ctoolbar\">");
+ if (UserQueries.isSubscribed(sql, visitor.UID, user.UID)) {
+ out.println(" <li><a href=\"/post?body=U+%40" + user.UName + "\" title=\"Подписан\"><div style=\"background-position: -48px 0\"></div></a></li>");
+ } else {
+ out.println(" <li><a href=\"/post?body=S+%40" + user.UName + "\" title=\"Подписаться\"><div style=\"background-position: -16px 0\"></div></a></li>");
+ }
+ if (UserQueries.isInBL(sql, visitor.UID, user.UID)) {
+ out.println(" <li><a href=\"/post?body=BL+%40" + user.UName + "\" title=\"Разблокировать\"><div style=\"background-position: -96px 0\"></div></a></li>");
+ } else {
+ out.println(" <li><a href=\"/post?body=BL+%40" + user.UName + "\" title=\"Заблокировать\"><div style=\"background-position: -80px 0\"></div></a></li>");
+ }
+ if (!UserQueries.isInBLAny(sql, user.UID, visitor.UID)) {
+ out.println(" <li><a href=\"/pm/sent?uname=" + user.UName + "\" title=\"Написать приватное сообщение\"><div style=\"background-position: -112px 0\"></div></a></li>");
+ }
+ out.println(" </ul>");
+ } else {
+ out.println(" <hr/>");
+ }
+ out.println(" <ul>");
+ out.println(" <li><a href=\"./\">Блог</a></li>");
+ out.println(" <li><a href=\"./?show=recomm\" rel=\"nofollow\">Рекомендации</a></li>");
+ out.println(" <li><a href=\"./?show=photos\" rel=\"nofollow\">Фотографии</a></li>");
+ out.println(" </ul>");
+ out.println(" <hr/>");
+ out.println(" <form action=\"./\">");
+ out.println(" <p><input type=\"text\" name=\"search\" class=\"inp\" placeholder=\"Поиск\"/></p>");
+ out.println(" </form>");
+ out.println(" <p class=\"tags\">" + pageUserTags(sql, user, visitor, 20) + "<a href=\"./tags\" rel=\"nofollow\">...</a></p>");
+ out.println(" <hr/>");
+ out.println(" <div id=\"ustats\"><ul>");
+ out.println(" <li><a href=\"./friends\">Я читаю: " + UserQueries.getStatsIRead(sql, user.UID) + "</a></li>");
+ out.println(" <li><a href=\"./readers\">Мои подписчики: " + UserQueries.getStatsMyReaders(sql, user.UID) + "</a></li>");
+ out.println(" <li>Сообщений: " + UserQueries.getStatsMessages(sql, user.UID) + "</li>");
+ out.println(" <li>Комментариев: " + UserQueries.getStatsReplies(sql, user.UID) + "</li>");
+ out.println(" </ul>");
+
+ ArrayList<com.juick.User> iread = UserQueries.getUserReadLeastPopular(sql, user.UID, 8);
+ if (!iread.isEmpty()) {
+ out.println("<table class=\"iread\"><tr>");
+ for (int i = 0; i < iread.size(); i++) {
+ if (i == 4) {
+ out.println("</tr><tr>");
+ }
+ com.juick.User u = iread.get(i);
+ out.println("<td><a href=\"/" + u.UName + "/\"><img src=\"//i.juick.com/a/" + u.UID + ".png\" alt=\"" + u.UName + "\"/></a></td>");
+ }
+ out.println("</tr></table>");
+ }
+
+ out.println(" </div>");
+ out.println("</aside>");
+ }
+
+ public static String pageUserTags(Connection sql, com.juick.User user, com.juick.User visitor, int cnt) {
+ com.juick.Tag tags[] = null;
+
+ int maxUsageCnt = 0;
+ PreparedStatement stmt = null;
+ ResultSet rs = null;
+ try {
+ if (cnt > 0) {
+ stmt = sql.prepareStatement("SELECT tags.name AS name,COUNT(DISTINCT messages_tags.message_id) AS cnt FROM (messages INNER JOIN messages_tags ON (messages.message_id=messages_tags.message_id)) INNER JOIN tags ON messages_tags.tag_id=tags.tag_id WHERE messages.user_id=? GROUP BY messages_tags.tag_id ORDER BY cnt DESC LIMIT ?", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
+ stmt.setInt(1, user.UID);
+ stmt.setInt(2, cnt);
+ } else {
+ stmt = sql.prepareStatement("SELECT tags.name AS name,COUNT(DISTINCT messages_tags.message_id) AS cnt FROM (messages INNER JOIN messages_tags ON (messages.message_id=messages_tags.message_id)) INNER JOIN tags ON messages_tags.tag_id=tags.tag_id WHERE messages.user_id=? GROUP BY messages_tags.tag_id ORDER BY cnt DESC", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
+ stmt.setInt(1, user.UID);
+ }
+ rs = stmt.executeQuery();
+ rs.last();
+ tags = new com.juick.Tag[rs.getRow()];
+ rs.beforeFirst();
+ cnt = 0;
+ while (rs.next()) {
+ tags[cnt] = new com.juick.Tag();
+ tags[cnt].Name = rs.getString(1);
+ tags[cnt].UsageCnt = rs.getInt(2);
+ if (tags[cnt].UsageCnt > maxUsageCnt) {
+ maxUsageCnt = tags[cnt].UsageCnt;
+ }
+ cnt++;
+ }
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(rs, stmt);
+ }
+
+ if (tags != null && cnt > 0) {
+ Arrays.sort(tags, 0, cnt);
+ }
+
+ String ret = "";
+ for (int i = 0; i < cnt; i++) {
+ String tag = Utils.encodeHTML(tags[i].Name);
+ try {
+ tag = "<a href=\"./?tag=" + URLEncoder.encode(tags[i].Name, "UTF-8") + "\" title=\"" + tags[i].UsageCnt + "\" rel=\"nofollow\">" + tag + "</a>";
+ } catch (UnsupportedEncodingException e) {
+ }
+
+ if (tags[i].UsageCnt > maxUsageCnt / 3 * 2) {
+ ret += "<big>" + tag + "</big> ";
+ } else if (tags[i].UsageCnt > maxUsageCnt / 3) {
+ ret += "<small>" + tag + "</small> ";
+ } else {
+ ret += tag + " ";
+ }
+ }
+ return ret;
+ }
+}
diff --git a/src/main/java/com/juick/http/www/UserThread.java b/src/main/java/com/juick/http/www/UserThread.java
new file mode 100644
index 00000000..73809f6c
--- /dev/null
+++ b/src/main/java/com/juick/http/www/UserThread.java
@@ -0,0 +1,370 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2011, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick.http.www;
+
+import com.juick.server.MessagesQueries;
+import com.juick.server.UserQueries;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.util.ArrayList;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class UserThread {
+
+ protected void doGetThread(Connection sql, HttpServletRequest request, HttpServletResponse response, int MID) throws ServletException, IOException {
+ com.juick.User visitor = Utils.getVisitorUser(sql, request, response);
+
+ if (!MessagesQueries.canViewThread(sql, MID, visitor != null ? visitor.UID : 0)) {
+ response.sendError(403);
+ return;
+ }
+
+ com.juick.Message msg = MessagesQueries.getMessage(sql, MID);
+
+ boolean listview = false;
+ String paramView = request.getParameter("view");
+ if (paramView != null) {
+ if (paramView.equals("list")) {
+ listview = true;
+ if (visitor != null) {
+ UserQueries.setUserOptionInt(sql, visitor.UID, "repliesview", 1);
+ }
+ } else if (paramView.equals("tree") && visitor != null) {
+ UserQueries.setUserOptionInt(sql, visitor.UID, "repliesview", 0);
+ }
+ } else if (visitor != null && UserQueries.getUserOptionInt(sql, visitor.UID, "repliesview", 0) == 1) {
+ listview = true;
+ }
+
+ String title = msg.User.UName + ": " + msg.getTagsString();
+
+ if (visitor == null) {
+ User.pageUserRefCookie(request, response, msg.User.UID);
+ }
+
+ response.setContentType("text/html; charset=UTF-8");
+ PrintWriter out = response.getWriter();
+ try {
+ String headers = "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"@" + msg.User.UName + "\" href=\"//rss.juick.com/" + msg.User.UName + "/blog\"/>";
+ if (paramView != null) {
+ headers += "<link rel=\"canonical\" href=\"http://juick.com/" + msg.User.UName + "/" + msg.MID + "\"/>";
+ }
+ if (msg.Hidden) {
+ headers += "<meta name=\"robots\" content=\"noindex\"/>";
+ }
+ PageTemplates.pageHead(out, title, headers);
+ PageTemplates.pageNavigation(out, visitor, null);
+
+ out.println("<section id=\"content\" style=\"margin-left: 0; width: 100%\">");
+ printMessage(out, sql, msg, visitor);
+ printReplies(out, sql, msg, visitor, listview);
+ out.println("</section>");
+
+ PageTemplates.pageFooter(request, out, visitor, false);
+
+ out.println("<script type='text/javascript'>");
+ if (visitor != null && visitor.UID == 1 && msg.User.UID == 1) {
+ out.println("var juickDebug=1;");
+ }
+ out.println("var pageMID=" + msg.MID + ";");
+ out.println("initWS();");
+ out.println("</script>");
+
+ PageTemplates.pageEnd(out);
+ } finally {
+ out.close();
+ }
+ }
+
+ public static com.juick.Message printMessage(PrintWriter out, Connection sql, com.juick.Message msg, com.juick.User visitor) {
+ msg.VisitorCanComment = visitor != null;
+
+ ArrayList<com.juick.Tag> tags = MessagesQueries.getMessageTags(sql, msg.MID);
+ String tagsStr = PageTemplates.formatTags(tags);
+ if (msg.ReadOnly) {
+ tagsStr += " *readonly";
+ msg.VisitorCanComment = false;
+ }
+ if (msg.Privacy < 0) {
+ tagsStr += " *friends";
+ }
+
+ String txt;
+ if (!msg.Tags.isEmpty() && msg.Tags.contains("code")) {
+ txt = PageTemplates.formatMessageCode(msg.Text);
+ } else {
+ txt = PageTemplates.formatMessage(msg.Text);
+ }
+
+ if (!tags.isEmpty()) {
+ tagsStr = "<span class=\"msg-tags\">" + tagsStr + "</span>";
+ }
+
+ out.println("<ul>");
+ out.println(" <li id=\"msg-" + msg.MID + "\" class=\"msg msgthread\">");
+ out.println(" <div class=\"msg-avatar\"><a href=\"/" + msg.User.UName + "/\"><img src=\"//i.juick.com/a/" + msg.User.UID + ".png\" alt=\"" + msg.User.UName + "\"/></a></div>");
+ out.println(" <div class=\"msg-cont\">");
+ out.println(" <div class=\"msg-menu\"><a href=\"#\" onclick=\"showMessageLinksDialog(" + msg.MID + "); return false\"></a></div>");
+ out.println(" <div class=\"msg-header\"><a href=\"/" + msg.User.UName + "/\">@" + msg.User.UName + "</a>:" + tagsStr + "</div>");
+ out.println(" <div class=\"msg-ts\">" + PageTemplates.formatJSLocalTime(msg.TimestampString) + "</div>");
+ out.println(" <div class=\"msg-txt\">" + txt + "</div>");
+
+ if (msg.AttachmentType != null) {
+ out.println(" <div class=\"msg-media\"><a href=\"//i.juick.com/p/" + msg.MID + "." + msg.AttachmentType + "\"><img src=\"//i.juick.com/photos-512/" + msg.MID + "." + msg.AttachmentType + "\" alt=\"\"/></a></div>");
+ }
+
+ boolean visitorInBL = false;
+ if (visitor != null) {
+ if (visitor.UID == msg.User.UID) {
+ msg.VisitorCanComment = true;
+ } else {
+ visitorInBL = UserQueries.isInBL(sql, msg.User.UID, visitor.UID);
+ if (visitorInBL) {
+ msg.VisitorCanComment = false;
+ }
+ }
+ }
+
+ if (msg.VisitorCanComment) {
+ out.println(" <form action=\"/comment\" method=\"POST\" enctype=\"multipart/form-data\"><input type=\"hidden\" name=\"mid\" value=\"" + msg.MID + "\"/>");
+ out.println(" <div class=\"msg-comment\"><div class=\"ta-wrapper\"><textarea name=\"body\" rows=\"1\" class=\"reply\" placeholder=\"Написать комментарий\" onkeypress=\"postformListener(this.form,event)\"></textarea></div></div>");
+ out.println(" </form>");
+ }
+
+ ArrayList<String> recomm = MessagesQueries.getMessageRecommendations(sql, msg.MID);
+ if (!recomm.isEmpty()) {
+ out.print(" <div class=\"" + (msg.VisitorCanComment ? "msg-recomms" : "msg-comments") + "\">Рекомендовали (" + recomm.size() + "): ");
+ for (int i = 0; i < recomm.size(); i++) {
+ if (i > 0) {
+ out.print(", ");
+ }
+ out.print("<a href=\"/" + recomm.get(i) + "/\">@" + recomm.get(i) + "</a>");
+ }
+ out.println("</div>");
+ }
+ out.println(" </div>");
+ out.println(" </li>");
+
+ out.println(" <li id=\"mtoolbar\"><ul>");
+ out.println(" <li><a href=\"/" + msg.MID + "\"><div style=\"background-position: -64px 0\"></div>" + msg.MID + "</a></li>");
+ if (visitor != null) {
+ if (visitor.UID != msg.User.UID) {
+ if (MessagesQueries.isSubscribed(sql, visitor.UID, msg.MID)) {
+ out.println(" <li><a href=\"/post?body=U+%23" + msg.MID + "\"><div style=\"background-position: -48px 0\"></div>Подписан</a></li>");
+ } else {
+ out.println(" <li><a href=\"/post?body=S+%23" + msg.MID + "\"><div style=\"background-position: -16px 0\"></div>Подписаться</a></li>");
+ }
+ if (!visitorInBL) {
+ out.println(" <li><a href=\"/post?body=%21+%23" + msg.MID + "\"><div style=\"background-position: -32px 0\"></div>Рекомендовать</a></li>");
+ }
+ } else {
+ out.println(" <li><a href=\"/post?body=D+%23" + msg.MID + "\"><div style=\"background-position: 0\"></div>Удалить</a></li>");
+ }
+ }
+ out.println(" </ul></li>");
+ out.println("</ul>");
+
+ return msg;
+ }
+
+ public static void printReplies(PrintWriter out, Connection sql, com.juick.Message msg, com.juick.User visitor, boolean listview) {
+ ArrayList<com.juick.Message> replies = MessagesQueries.getReplies(sql, msg.MID);
+
+ ArrayList<Integer> blUIDs = new ArrayList<Integer>();
+ for (int i = 0; i < replies.size(); i++) {
+ com.juick.Message reply = replies.get(i);
+ if (reply.User.UID != msg.User.UID && !blUIDs.contains(reply.User.UID)) {
+ blUIDs.add(reply.User.UID);
+ }
+ if (reply.ReplyTo > 0) {
+ boolean added = false;
+ for (int n = 0; n < replies.size(); n++) {
+ if (replies.get(n).RID == reply.ReplyTo) {
+ replies.get(n).childs.add(reply);
+ added = true;
+ break;
+ }
+ }
+ if (!added) {
+ reply.ReplyTo = 0;
+ }
+ }
+ }
+
+ if (!replies.isEmpty()) {
+ if (visitor != null && msg.User.UID == visitor.UID) {
+ for (int i = 0; i < replies.size(); i++) {
+ replies.get(i).VisitorCanComment = true;
+ }
+ } else if (visitor != null && msg.VisitorCanComment) {
+ blUIDs = UserQueries.checkBL(sql, visitor.UID, blUIDs);
+ for (int i = 0; i < replies.size(); i++) {
+ com.juick.Message reply = replies.get(i);
+ reply.VisitorCanComment = reply.User.UID == visitor.UID || !blUIDs.contains(reply.User.UID);
+ }
+ } else {
+ for (int i = 0; i < replies.size(); i++) {
+ replies.get(i).VisitorCanComment = false;
+ }
+ }
+
+ boolean foldable = false;
+ if (replies.size() > 10) {
+ for (int i = 0; i < replies.size() - 1; i++) {
+ if (replies.get(i).getChildsCount() > 1) {
+ foldable = true;
+ break;
+ }
+ }
+ }
+
+ out.println("<div class=\"title2\">");
+ out.print(" <div class=\"title2-right\">");
+ if (listview) {
+ out.print("<a href=\"?view=tree\" rel=\"nofollow\">Показать деревом</a>");
+ } else {
+ if (foldable) {
+ out.print("<span id=\"unfoldall\"><a href=\"#\" onclick=\"$('#replies>li').show(); $('#replies .msg-comments').hide(); $('#unfoldall').hide(); return false\">Раскрыть все</a> &#183; </span>");
+ }
+ out.print("<a href=\"?view=list\" rel=\"nofollow\">Показать списком</a>");
+ }
+ out.print("</div>");
+ out.println(" <h2>Ответы (" + replies.size() + ")</h2>");
+ out.println("</div>");
+
+ out.println("<ul id=\"replies\">");
+ if (listview) {
+ printList(out, replies, visitor);
+ } else {
+ printTree(out, replies, visitor, 0, 0, false);
+ }
+ out.println("</ul>");
+
+ if (replies.size() > 0) {
+ PageTemplates.pageYandexAd728(out, 1);
+ }
+
+ for (int i = 0; i < replies.size(); i++) {
+ replies.get(i).cleanupChilds();
+ }
+ replies.clear();
+ }
+ }
+
+ public static void printTree(PrintWriter out, ArrayList<com.juick.Message> replies, com.juick.User visitor, int ReplyTo, int margin, boolean hidden) {
+ if (margin > 240) {
+ margin = 240;
+ }
+
+ for (int i = 0; i < replies.size(); i++) {
+ com.juick.Message msg = replies.get(i);
+ if (msg.ReplyTo == ReplyTo) {
+
+ out.print(" <li id=\"" + msg.RID + "\" class=\"msg\" style=\"");
+ if (margin > 0) {
+ out.print("margin-left: " + margin + "px;");
+ }
+ if (hidden) {
+ out.print("display:none;");
+ }
+ out.println("\">");
+ if (msg.User.Banned == false) {
+ out.println(" <div class=\"msg-avatar\"><a href=\"/" + msg.User.UName + "/\"><img src=\"//i.juick.com/a/" + msg.User.UID + ".png\" alt=\"" + msg.User.UName + "\"/></a></div>");
+ } else {
+ out.println(" <div class=\"msg-avatar\"><img src=\"//i.juick.com/av-96.png\"/></div>");
+ }
+ out.println(" <div class=\"msg-cont\">");
+ out.println(" <div class=\"msg-menu\"><a href=\"#\" onclick=\"showMessageLinksDialog(" + msg.MID + "," + msg.RID + "); return false\"></a></div>");
+ if (msg.User.Banned == false) {
+ out.println(" <div class=\"msg-header\"><a href=\"/" + msg.User.UName + "/\">@" + msg.User.UName + "</a>:</div>");
+ } else {
+ out.println(" <div class=\"msg-header\">[удалено]:</div>");
+ }
+ out.println(" <div class=\"msg-ts\"><a href=\"/" + msg.MID + "#" + msg.RID + "\" title=\"" + msg.TimestampString + " GMT\">" + PageTemplates.formatDate(msg.TimeAgo, msg.TimestampString) + "</a></div>");
+ out.println(" <div class=\"msg-txt\">" + PageTemplates.formatMessage(msg.Text) + "</div>");
+ if (msg.AttachmentType != null) {
+ out.println(" <div class=\"msg-media\"><a href=\"//i.juick.com/p/" + msg.MID + "-" + msg.RID + "." + msg.AttachmentType + "\"><img src=\"//i.juick.com/photos-512/" + msg.MID + "-" + msg.RID + "." + msg.AttachmentType + "\" alt=\"\"/></a></div>");
+ }
+ if (msg.VisitorCanComment) {
+ out.println(" <div class=\"msg-links\"><a href=\"#\" onclick=\"return showCommentForm(" + msg.MID + "," + msg.RID + ")\">Ответить</a></div>");
+ out.println(" <div class=\"msg-comment\" style=\"display: none\"></div>");
+ } else if (visitor == null) {
+ out.println(" <div class=\"msg-links\"><a href=\"#\" onclick=\"return openDialogLogin()\">Ответить</a></div>");
+ }
+
+ int childs = msg.getChildsCount();
+ if (ReplyTo == 0 && childs > 1 && replies.size() > 10) {
+ out.println(" <div class=\"msg-comments\"><a href=\"#\" onclick=\"return showMoreReplies(" + msg.RID + ")\">" + PageTemplates.formatReplies(childs) + "</a></div>");
+
+ }
+ out.println(" </div>");
+ out.println(" </li>");
+
+ if (ReplyTo == 0 && childs > 1 && replies.size() > 10) {
+ printTree(out, msg.childs, visitor, msg.RID, margin + 20, true);
+ } else if (childs > 0) {
+ printTree(out, msg.childs, visitor, msg.RID, margin + 20, hidden);
+ }
+ }
+ }
+ }
+
+ public static void printList(PrintWriter out, ArrayList<com.juick.Message> replies, com.juick.User visitor) {
+ for (int i = 0; i < replies.size(); i++) {
+ com.juick.Message msg = replies.get(i);
+
+ out.print(" <li id=\"" + msg.RID + "\" class=\"msg\">");
+ if (msg.User.Banned == false) {
+ out.println(" <div class=\"msg-avatar\"><a href=\"/" + msg.User.UName + "/\"><img src=\"//i.juick.com/a/" + msg.User.UID + ".png\" alt=\"" + msg.User.UName + "\"/></a></div>");
+ } else {
+ out.println(" <div class=\"msg-avatar\"><img src=\"//i.juick.com/av-96.png\"/></div>");
+ }
+ out.println(" <div class=\"msg-cont\">");
+ out.println(" <div class=\"msg-menu\"><a href=\"#\" onclick=\"showMessageLinksDialog(" + msg.MID + "," + msg.RID + "); return false\"></a></div>");
+ if (msg.User.Banned == false) {
+ out.println(" <div class=\"msg-header\"><a href=\"/" + msg.User.UName + "/\">@" + msg.User.UName + "</a>:</div>");
+ } else {
+ out.println(" <div class=\"msg-header\">[удалено]:</div>");
+ }
+ out.println(" <div class=\"msg-ts\"><a href=\"/" + msg.MID + "#" + msg.RID + "\" title=\"" + msg.TimestampString + " GMT\">" + PageTemplates.formatDate(msg.TimeAgo, msg.TimestampString) + "</a></div>");
+ out.println(" <div class=\"msg-txt\">" + PageTemplates.formatMessage(msg.Text) + "</div>");
+ if (msg.AttachmentType != null) {
+ out.println(" <div class=\"msg-media\"><a href=\"//i.juick.com/p/" + msg.MID + "-" + msg.RID + "." + msg.AttachmentType + "\"><img src=\"//i.juick.com/photos-512/" + msg.MID + "-" + msg.RID + "." + msg.AttachmentType + "\" alt=\"\"/></a></div>");
+ }
+ out.print(" <div class=\"msg-links\">/" + msg.RID);
+ if (msg.ReplyTo > 0) {
+ out.print(" в ответ на <a href=\"#" + msg.ReplyTo + "\">/" + msg.ReplyTo + "</a>");
+ }
+ if (msg.VisitorCanComment) {
+ out.println(" &#183; <a href=\"#\" onclick=\"return showCommentForm(" + msg.MID + "," + msg.RID + ")\">Ответить</a></div>");
+ out.println(" <div class=\"msg-comment\" style=\"display: none\"></div>");
+ } else if (visitor == null) {
+ out.println(" <div class=\"msg-links\"><a href=\"#\" onclick=\"return openDialogLogin()\">Ответить</a></div>");
+ }
+ out.println(" </div>");
+ out.println(" </li>");
+ }
+ }
+}
diff --git a/src/main/java/com/juick/http/www/Utils.java b/src/main/java/com/juick/http/www/Utils.java
new file mode 100644
index 00000000..afaf131e
--- /dev/null
+++ b/src/main/java/com/juick/http/www/Utils.java
@@ -0,0 +1,244 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2011, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick.http.www;
+
+import java.io.BufferedReader;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.net.URLConnection;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.UUID;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.Part;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class Utils {
+
+ public static String getCookie(HttpServletRequest request, String name) {
+ Cookie cookies[] = request.getCookies();
+ if (cookies != null) {
+ for (int i = 0; i < cookies.length; i++) {
+ if (cookies[i].getName().equals(name)) {
+ return cookies[i].getValue();
+ }
+ }
+ }
+ return null;
+ }
+
+ public static String receiveMultiPartFile(HttpServletRequest request, String name) throws Exception {
+ String attachmentFName = null;
+
+ Part filePart = request.getPart("attach");
+ if (filePart != null) {
+ String partname = Utils.getPartFilename(filePart);
+ String attachmentType = partname.substring(partname.length() - 3).toLowerCase();
+ if (attachmentType.equals("jpg") || attachmentType.equals("peg") || attachmentType.equals("png")) {
+ if (attachmentType.equals("peg")) {
+ attachmentType = "jpg";
+ }
+ attachmentFName = UUID.randomUUID().toString() + "." + attachmentType;
+ filePart.write("/var/www/juick.com/i/tmp/" + attachmentFName);
+ } else {
+ throw new Exception("Wrong file type");
+ }
+ }
+
+ return attachmentFName;
+ }
+
+ public static com.juick.User getVisitorUser(Connection sql, HttpServletRequest request, HttpServletResponse response) {
+ String hash = getCookie(request, "hash");
+ if (hash != null) {
+ com.juick.User visitor = com.juick.server.UserQueries.getUserByHash(sql, hash);
+ if (response != null && visitor != null) {
+ response.setHeader("X-Username", visitor.UName);
+ }
+ return visitor;
+ } else {
+ return null;
+ }
+ }
+
+ public static void sendTemporaryRedirect(HttpServletResponse response, String location) {
+ response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
+ response.setHeader("Location", location);
+ }
+
+ public static void sendPermanentRedirect(HttpServletResponse response, String location) {
+ response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
+ response.setHeader("Location", location);
+ }
+
+ public static String getPartFilename(Part part) {
+ for (String cd : part.getHeader("content-disposition").split(";")) {
+ if (cd.trim().startsWith("filename")) {
+ String filename = cd.substring(cd.indexOf('=') + 1).trim().replace("\"", "");
+ return filename.substring(filename.lastIndexOf('/') + 1).substring(filename.lastIndexOf('\\') + 1); // MSIE fix.
+ }
+ }
+ return null;
+ }
+
+ public static void finishSQL(ResultSet rs, Statement stmt) {
+ if (rs != null) {
+ try {
+ rs.close();
+ } catch (SQLException e) {
+ }
+ }
+ if (stmt != null) {
+ try {
+ stmt.close();
+ } catch (SQLException e) {
+ }
+ }
+ }
+
+ public static void replyJSON(HttpServletRequest request, HttpServletResponse response, String json) throws IOException {
+ response.setContentType("application/json; charset=UTF-8");
+ response.setHeader("Access-Control-Allow-Origin", "*");
+
+ String callback = request.getParameter("callback");
+ if (callback != null && (callback.length() > 64 || !callback.matches("[a-zA-Z0-9\\-\\_]+"))) {
+ callback = null;
+ }
+
+ PrintWriter out = response.getWriter();
+ try {
+ if (callback != null) {
+ out.print(callback + "(");
+ out.print(json);
+ out.print(")");
+ } else {
+ out.print(json);
+ }
+ } finally {
+ out.close();
+ }
+ }
+
+ public static String convertArray2String(ArrayList<Integer> mids) {
+ String q = "";
+ for (int i = 0; i < mids.size(); i++) {
+ if (i > 0) {
+ q += ",";
+ }
+ q += mids.get(i);
+ }
+ return q;
+ }
+
+ public static String encodeHTML(String str) {
+ return str.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("'", "&apos;").replaceAll("\"", "&quot;").replaceAll("\n", "&#10;");
+ }
+
+ public static String encodeSphinx(String str) {
+ return str.replaceAll("@", "\\\\@");
+ }
+
+ public static int parseInt(String str, int def) {
+ int ret = def;
+ if (str != null) {
+ try {
+ ret = Integer.parseInt(str);
+ } catch (Exception e) {
+ }
+ }
+ return ret;
+ }
+
+ public static String fetchURL(String url) {
+ try {
+ URLConnection c = new URL(url).openConnection();
+ BufferedReader in = new BufferedReader(new InputStreamReader(c.getInputStream()));
+ String inputLine;
+ StringBuilder b = new StringBuilder();
+ while ((inputLine = in.readLine()) != null) {
+ b.append(inputLine).append("\n");
+ }
+ in.close();
+ return b.toString();
+ } catch (Exception e) {
+ System.err.println("fetchURL: "+e.toString());
+ return null;
+ }
+ }
+
+ public static String downloadImage(String url) throws Exception {
+ String attachmentFName = null;
+ Exception ex = null;
+
+ InputStream is = null;
+ FileOutputStream fos = null;
+ try {
+ URLConnection urlConn = new URL(url).openConnection();
+ is = urlConn.getInputStream();
+ String mime = urlConn.getContentType();
+
+ String attachmentType;
+ if (mime != null && mime.equals("image/jpeg")) {
+ attachmentType = "jpg";
+ } else if (mime != null && mime.equals("image/png")) {
+ attachmentType = "png";
+ } else {
+ throw new Exception("Wrong file type");
+ }
+
+ attachmentFName = UUID.randomUUID().toString() + "." + attachmentType;
+ fos = new FileOutputStream("/var/www/juick.com/i/tmp/" + attachmentFName);
+ byte[] buffer = new byte[10240];
+ int len;
+ while ((len = is.read(buffer)) > 0) {
+ fos.write(buffer, 0, len);
+ }
+ } catch (Exception e) {
+ ex = e;
+ attachmentFName = null;
+ } finally {
+ try {
+ if (is != null) {
+ is.close();
+ }
+ } finally {
+ if (fos != null) {
+ fos.close();
+ }
+ }
+ }
+
+ if (ex != null) {
+ throw ex;
+ } else {
+ return attachmentFName;
+ }
+ }
+}
diff --git a/src/main/java/com/juick/http/www/VKontakteLogin.java b/src/main/java/com/juick/http/www/VKontakteLogin.java
new file mode 100644
index 00000000..5f26fef1
--- /dev/null
+++ b/src/main/java/com/juick/http/www/VKontakteLogin.java
@@ -0,0 +1,155 @@
+/*
+ * Juick
+ * Copyright (C) 2008-2013, Ugnich Anton
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.juick.http.www;
+
+import com.juick.server.UserQueries;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ *
+ * @author Ugnich Anton
+ */
+public class VKontakteLogin {
+
+ private static final String VK_APPID = "3544101";
+ private static final String VK_SECRET = "z2afNI8jA5lIpZ2jsTm1";
+ private static final String VK_REDIRECT = "http://juick.com/_vklogin";
+
+ protected void doGet(Connection sql, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ String code = request.getParameter("code");
+ if (code == null || code.equals("")) {
+ response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
+ response.setHeader("Location", "https://oauth.vk.com/authorize?client_id=" + VK_APPID + "&redirect_uri=" + URLEncoder.encode(VK_REDIRECT, "utf-8") + "&scope=friends,wall,offline&response_type=code");
+ return;
+ }
+
+
+ String tokenjson = Utils.fetchURL("https://oauth.vk.com/access_token?client_id=" + VK_APPID + "&redirect_uri=" + URLEncoder.encode(VK_REDIRECT, "utf-8") + "&client_secret=" + VK_SECRET + "&code=" + URLEncoder.encode(code, "utf-8"));
+ if (tokenjson == null || tokenjson.isEmpty()) {
+ System.err.println("VK TOKEN EMPTY");
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ String token = null;
+ long vkID = 0;
+ try {
+ JSONObject json = new JSONObject(tokenjson);
+ token = json.getString("access_token");
+ vkID = json.getLong("user_id");
+ } catch (JSONException e) {
+ System.err.println("VK TOKEN EXCEPTION: " + e);
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ if (token == null || vkID == 0) {
+ System.err.println("VK TOKEN EMPTY: " + tokenjson);
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+
+
+ String graph = Utils.fetchURL("https://api.vk.com/method/users.get?uids=" + vkID + "&fields=screen_name&access_token=" + token);
+ if (graph == null || graph.isEmpty()) {
+ System.err.println("VK GRAPH ERROR");
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ try {
+ JSONObject json = new JSONObject(graph).getJSONArray("response").getJSONObject(0);
+ String vkName = json.getString("first_name") + " " + json.getString("last_name");
+ String vkLink = json.getString("screen_name");
+
+ if (vkName == null || vkLink == null || vkName.isEmpty() || vkName.length() == 1 || vkLink.isEmpty()) {
+ throw new Exception();
+ }
+
+ int uid = getUIDbyVKID(sql, vkID);
+ if (uid > 0) {
+ Cookie c = new Cookie("hash", UserQueries.getHashByUID(sql, uid));
+ c.setMaxAge(50 * 24 * 60 * 60);
+ response.addCookie(c);
+ response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
+ response.setHeader("Location", "/");
+ } else {
+ String loginhash = UUID.randomUUID().toString();
+ if (!insertDB(sql, vkID, loginhash, token, vkName, vkLink)) {
+ throw new Exception();
+ }
+ response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
+ response.setHeader("Location", "/signup?type=vk&hash=" + loginhash);
+ }
+ } catch (Exception e) {
+ System.err.println("JSON ERROR: " + e);
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ }
+
+ private int getUIDbyVKID(Connection sql, long vkID) {
+ int uid = 0;
+ PreparedStatement stmt = null;
+ ResultSet rs = null;
+ try {
+ stmt = sql.prepareStatement("SELECT user_id FROM vk WHERE vk_id=? AND user_id IS NOT NULL");
+ stmt.setLong(1, vkID);
+ rs = stmt.executeQuery();
+ if (rs.first()) {
+ uid = rs.getInt(1);
+ }
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(rs, stmt);
+ }
+ return uid;
+ }
+
+ private boolean insertDB(Connection sql, long vkID, String loginhash, String token, String vkName, String vkLink) {
+ boolean ret = false;
+ PreparedStatement stmt = null;
+ try {
+ stmt = sql.prepareStatement("INSERT INTO vk(vk_id,loginhash,access_token,vk_name,vk_link) VALUES (?,?,?,?,?)");
+ stmt.setLong(1, vkID);
+ stmt.setString(2, loginhash);
+ stmt.setString(3, token);
+ stmt.setString(4, vkName);
+ stmt.setString(5, vkLink);
+ stmt.executeUpdate();
+ ret = true;
+ } catch (SQLException e) {
+ System.err.println(e);
+ } finally {
+ Utils.finishSQL(null, stmt);
+ }
+ return ret;
+ }
+}
diff --git a/src/main/java/ru/sape/Sape.java b/src/main/java/ru/sape/Sape.java
new file mode 100644
index 00000000..c00054ae
--- /dev/null
+++ b/src/main/java/ru/sape/Sape.java
@@ -0,0 +1,25 @@
+/*
+ * http://code.google.com/p/javasape/
+ */
+package ru.sape;
+
+import javax.servlet.http.Cookie;
+
+public class Sape {
+
+ private final String sapeUser;
+ private final SapeConnection sapePageLinkConnection;
+
+ public Sape(String sapeUser, String host, int socketTimeout, int cacheLifeTime) {
+ this.sapeUser = sapeUser;
+
+ this.sapePageLinkConnection = new SapeConnection(
+ "/code.php?user=" + sapeUser + "&host=" + host,
+ "SAPE_Client PHP", socketTimeout, cacheLifeTime);
+ }
+ public boolean debug = false;
+
+ public SapePageLinks getPageLinks(String requestUri, Cookie[] cookies) {
+ return new SapePageLinks(sapePageLinkConnection, sapeUser, requestUri, cookies, debug);
+ }
+}
diff --git a/src/main/java/ru/sape/SapeConnection.java b/src/main/java/ru/sape/SapeConnection.java
new file mode 100644
index 00000000..8c794b08
--- /dev/null
+++ b/src/main/java/ru/sape/SapeConnection.java
@@ -0,0 +1,107 @@
+package ru.sape;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class SapeConnection {
+
+ private final String version = "1.0.3";
+ private final List<String> serverList = Arrays.asList("dispenser-01.sape.ru", "dispenser-02.sape.ru");
+ private final String dispenserPath;
+ private final String userAgent;
+ private final int socketTimeout;
+ private final int cacheLifeTime;
+
+ public SapeConnection(String dispenserPath, String userAgent, int socketTimeout, int cacheLifeTime) {
+ this.dispenserPath = dispenserPath;
+ this.userAgent = userAgent;
+ this.socketTimeout = socketTimeout;
+ this.cacheLifeTime = cacheLifeTime;
+ }
+
+ protected String fetchRemoteFile(String host, String path) throws IOException {
+ Reader r = null;
+
+ try {
+ HttpURLConnection connection = (HttpURLConnection) ((new URL(("http://" + host + path)).openConnection()));
+
+ if (socketTimeout > 0) {
+ connection.setConnectTimeout(socketTimeout);
+ connection.setReadTimeout(socketTimeout);
+ }
+
+ connection.addRequestProperty("User-Agent", userAgent + ' ' + version);
+
+ connection.setDoOutput(true);
+ connection.setDoInput(true);
+ connection.setUseCaches(false);
+ connection.setRequestMethod("GET");
+ connection.connect();
+
+ r = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
+
+ StringWriter sw = new StringWriter();
+
+ int b;
+
+ while ((b = r.read()) != -1) {
+ sw.write(b);
+ }
+
+ return sw.toString();
+ } finally {
+ if (r != null) {
+ r.close();
+ }
+ }
+ }
+ Map<String, Object> cached;
+ long cacheUpdated;
+
+ @SuppressWarnings("unchecked")
+ public Map<String, Object> getData() {
+ if (cacheLifeTime <= (System.currentTimeMillis() - cacheUpdated) / 1000) {
+ for (String server : serverList) {
+ String data;
+
+ try {
+ data = fetchRemoteFile(server, dispenserPath + "&charset=UTF-8");
+ } catch (IOException e1) {
+ continue;
+ }
+
+ if (data.startsWith("FATAL ERROR:")) {
+ System.err.println("Sape responded with error: " + data);
+
+ continue;
+ }
+
+ try {
+ cached = (Map<String, Object>) new SerializedPhpParser(data).parse();
+ } catch (Exception e) {
+ System.err.println("Can't parse Sape data: " + e);
+ continue;
+ }
+
+ cacheUpdated = System.currentTimeMillis();
+
+ return cached;
+ }
+
+ System.err.println("Unable to fetch Sape data");
+
+ return new HashMap<String, Object>();
+ }
+
+ return cached;
+ }
+}
diff --git a/src/main/java/ru/sape/SapePageLinks.java b/src/main/java/ru/sape/SapePageLinks.java
new file mode 100644
index 00000000..498aeac0
--- /dev/null
+++ b/src/main/java/ru/sape/SapePageLinks.java
@@ -0,0 +1,95 @@
+package ru.sape;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.http.Cookie;
+
+public class SapePageLinks {
+
+ private boolean showCode;
+
+ public SapePageLinks(SapeConnection sapeConnection, String sapeUser, String requestUri, Cookie[] cookies) {
+ this(sapeConnection, sapeUser, requestUri, cookies, false);
+ }
+
+ @SuppressWarnings("unchecked")
+ public SapePageLinks(SapeConnection sapeConnection, String sapeUser, String requestUri, Cookie[] cookies, boolean showCode) {
+ if (sapeUser.equals(getCookieValue(cookies, "sape_cookie"))) {
+ showCode = true;
+ }
+
+ Map<String, Object> data = sapeConnection.getData();
+
+ if (data.containsKey("__sape_delimiter__")) {
+ linkDelimiter = (String) data.get("__sape_delimiter__");
+ }
+
+ if (data.containsKey(requestUri)) {
+ pageLinks = new ArrayList<String>(((Map<Object, String>) data.get(requestUri)).values());
+ }
+
+ if (data.containsKey("__sape_new_url__")) {
+ if (showCode) {
+ Object newUrl = data.get("__sape_new_url__");
+
+ if (newUrl instanceof Map) {
+ pageLinks = new ArrayList<String>(((Map<Object, String>) newUrl).values());
+ } else {
+ pageLinks = new ArrayList<String>(Arrays.asList((String) newUrl));
+ }
+ }
+ }
+
+ this.showCode = showCode;
+ }
+ private String linkDelimiter = ".";
+ private List<String> pageLinks = new ArrayList<String>();
+
+ public String render() {
+ return render(-1);
+ }
+
+ public String render(int count) {
+ StringBuilder s = new StringBuilder();
+
+ if (count < 0) {
+ count = pageLinks.size();
+ }
+
+ for (Iterator<String> i = pageLinks.iterator(); i.hasNext() && count > 0; count--) {
+ if (s.length() > 0) {
+ s.append(linkDelimiter);
+ }
+
+ String l = i.next();
+
+ s.append(l);
+
+ i.remove();
+ }
+
+ if (showCode) {
+ s.insert(0, "<sape_noindex>");
+ s.append("</sape_noindex>");
+ }
+
+ return s.toString();
+ }
+
+ private static String getCookieValue(Cookie[] cookies, String name) {
+ if (cookies == null) {
+ return null;
+ }
+
+ for (Cookie cookie : cookies) {
+ if (cookie.getName().equals(name)) {
+ return cookie.getValue();
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/ru/sape/SerializedPhpParser.java b/src/main/java/ru/sape/SerializedPhpParser.java
new file mode 100644
index 00000000..a24551b9
--- /dev/null
+++ b/src/main/java/ru/sape/SerializedPhpParser.java
@@ -0,0 +1,221 @@
+/*
+Copyright (c) 2007 Zsolt Szász <zsolt at lorecraft dot com>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package ru.sape;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Deserializes a serialized PHP data structure into corresponding Java objects. It supports
+ * the integer, float, boolean, string primitives that are mapped to their Java
+ * equivalent, plus arrays that are parsed into <code>Map</code> instances and objects
+ * that are represented by {@link SerializedPhpParser.PhpObject} instances.
+ * <p>
+ * Example of use:
+ * <pre>
+ * String input = "O:8:"TypeName":1:{s:3:"foo";s:3:"bar";}";
+ * SerializedPhpParser serializedPhpParser = new SerializedPhpParser(input);
+ * Object result = serializedPhpParser.parse();
+ * </pre>
+ *
+ * The <code>result</code> object will be a <code>PhpObject</code> with the name "TypeName" and
+ * the attribute "foo" = "bar".
+ */
+class SerializedPhpParser {
+
+ private final String input;
+ private int index;
+ private boolean assumeUTF8 = true;
+ private Pattern acceptedAttributeNameRegex = null;
+
+ public SerializedPhpParser(String input) {
+ this.input = input;
+ }
+
+ public Object parse() {
+ char type = input.charAt(index);
+ switch (type) {
+ case 'i':
+ index += 2;
+ return parseInt();
+ case 'd':
+ index += 2;
+ return parseFloat();
+ case 'b':
+ index += 2;
+ return parseBoolean();
+ case 's':
+ index += 2;
+ return parseString();
+ case 'a':
+ index += 2;
+ return parseArray();
+ case 'O':
+ index += 2;
+ return parseObject();
+ case 'N':
+ index += 2;
+ return NULL;
+ default:
+ throw new IllegalStateException("Encountered unknown type [" + type + "], str=" + input.substring(index));
+ }
+ }
+
+ private Object parseObject() {
+ PhpObject phpObject = new PhpObject();
+ int strLen = readLength();
+ phpObject.name = input.substring(index, index + strLen);
+ index = index + strLen + 2;
+ int attrLen = readLength();
+ for (int i = 0; i < attrLen; i++) {
+ Object key = parse();
+ Object value = parse();
+ if (isAcceptedAttribute(key)) {
+ phpObject.attributes.put(key, value);
+ }
+ }
+ index++;
+ return phpObject;
+ }
+
+ private Map<Object, Object> parseArray() {
+ int arrayLen = readLength();
+ Map<Object, Object> result = new LinkedHashMap<Object, Object>();
+ for (int i = 0; i < arrayLen; i++) {
+ Object key = parse();
+ Object value = parse();
+ if (isAcceptedAttribute(key)) {
+ result.put(key, value);
+ }
+ }
+ index++;
+ return result;
+ }
+
+ private boolean isAcceptedAttribute(Object key) {
+ if (acceptedAttributeNameRegex == null) {
+ return true;
+ }
+ if (!(key instanceof String)) {
+ return true;
+ }
+ return acceptedAttributeNameRegex.matcher((String) key).matches();
+ }
+
+ private int readLength() {
+ int delimiter = input.indexOf(':', index);
+ int arrayLen = Integer.valueOf(input.substring(index, delimiter));
+ index = delimiter + 2;
+ return arrayLen;
+ }
+
+ /**
+ * Assumes strings are utf8 encoded
+ *
+ * @return
+ */
+ private String parseString() {
+ int strLen = readLength();
+
+ int utfStrLen = 0;
+ int byteCount = 0;
+ while (byteCount != strLen) {
+ char ch = input.charAt(index + utfStrLen++);
+
+ /*
+ if (ch == '\'') {
+ utfStrLen -= 1;
+ break;
+ }
+ */
+
+ if (assumeUTF8) {
+ if ((ch >= 0x0001) && (ch <= 0x007F)) {
+ byteCount++;
+ } else if (ch > 0x07FF) {
+ byteCount += 3;
+ } else {
+ byteCount += 2;
+ }
+ } else {
+ byteCount++;
+ }
+ }
+ String value = input.substring(index, index + utfStrLen);
+ index = index + utfStrLen + 2;
+ return value;
+ }
+
+ private Boolean parseBoolean() {
+ int delimiter = input.indexOf(';', index);
+ String value = input.substring(index, delimiter);
+ if (value.equals("1")) {
+ value = "true";
+ } else if (value.equals("0")) {
+ value = "false";
+ }
+ index = delimiter + 1;
+ return Boolean.valueOf(value);
+ }
+
+ private Double parseFloat() {
+ int delimiter = input.indexOf(';', index);
+ String value = input.substring(index, delimiter);
+ index = delimiter + 1;
+ return Double.valueOf(value);
+ }
+
+ private Integer parseInt() {
+ int delimiter = input.indexOf(';', index);
+ String value = input.substring(index, delimiter);
+ index = delimiter + 1;
+ return Integer.valueOf(value);
+ }
+
+ public void setAcceptedAttributeNameRegex(String acceptedAttributeNameRegex) {
+ this.acceptedAttributeNameRegex = Pattern.compile(acceptedAttributeNameRegex);
+ }
+ public static final Object NULL = new Object() {
+
+ @Override
+ public String toString() {
+ return "NULL";
+ }
+ };
+
+ /**
+ * Represents an object that has a name and a map of attributes
+ */
+ public static class PhpObject {
+
+ public String name;
+ public Map<Object, Object> attributes = new HashMap<Object, Object>();
+
+ @Override
+ public String toString() {
+ return "\"" + name + "\" : " + attributes.toString();
+ }
+ }
+}
diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000..16e4d30f
--- /dev/null
+++ b/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
+ <servlet>
+ <servlet-name>Main</servlet-name>
+ <servlet-class>com.juick.http.www.Main</servlet-class>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>Main</servlet-name>
+ <url-pattern>/</url-pattern>
+ </servlet-mapping>
+ <session-config>
+ <session-timeout>
+ 30
+ </session-timeout>
+ </session-config>
+</web-app>
diff --git a/src/main/webapp/favicon.png b/src/main/webapp/favicon.png
new file mode 100644
index 00000000..bc7161e2
--- /dev/null
+++ b/src/main/webapp/favicon.png
Binary files differ
diff --git a/src/main/webapp/logo.png b/src/main/webapp/logo.png
new file mode 100644
index 00000000..933f6099
--- /dev/null
+++ b/src/main/webapp/logo.png
Binary files differ
diff --git a/src/main/webapp/scripts.js b/src/main/webapp/scripts.js
new file mode 100644
index 00000000..50c8b8d0
--- /dev/null
+++ b/src/main/webapp/scripts.js
@@ -0,0 +1,684 @@
+var ws=null;
+var pageTitle;
+
+function initWS() {
+ if(typeof(pageMID)!="undefined" && pageMID>0) {
+ var url;
+ if(typeof(juickDebug)!="undefined") {
+ url="ws://ws.juick.com/_replies";
+ } else {
+ url="ws://ws.juick.com/"+pageMID;
+ }
+ if(typeof(hash)!="undefined" && hash) {
+ url+="?hash="+hash;
+ }
+
+ ws = new WebSocket(url);
+ ws.onopen = function() {
+ console.log('online');
+ if($('#wsthread').length==0) {
+ var d=$('<div id="wsthread"/>');
+ d.on('click',onclickNextReply);
+ d.appendTo("body");
+ pageTitle=document.title;
+ }
+ };
+ ws.onclose = function() {
+ console.log('offline');
+ ws=null;
+ setTimeout(function() {
+ initWS();
+ },2000);
+ };
+ ws.onmessage = function(msg) {
+ if(msg.data==' ') {
+ ws.send(' ');
+ } else {
+ try {
+ var jsonMsg=$.parseJSON(msg.data);
+ console.log('data: '+msg.data);
+ wsIncomingReply(jsonMsg);
+ } catch(err) {
+ console.log(err);
+ }
+ }
+ };
+ setInterval(wsSendKeepAlive, 90000);
+ }
+}
+
+function wsSendKeepAlive() {
+ if(ws) {
+ ws.send(' ');
+ }
+}
+
+function wsShutdown() {
+ if(ws) {
+ ws.onclose=function(){};
+ ws.close();
+ }
+}
+
+function wsIncomingReply(msg) {
+ var p;
+ if(msg.replyto>0) {
+ p=$('#'+msg.replyto);
+ if(p.length==0) {
+ p=null;
+ }
+ }
+
+ var li=$('<li class="msg reply-new" id="'+msg.rid+'" onclick="onclickNewReply(this)" onmouseover="onclickNewReply(this)">');
+ li.html('<div class="msg-avatar"><a href="/'+msg.user.uname+'/"><img src="//i.juick.com/a/'+msg.user.uid+'.png"/></a></div>'+
+ '<div class="msg-cont">'+
+ '<div class="msg-menu"><a href="#" onclick="showMessageLinksDialog('+msg.mid+','+msg.rid+'); return false"></a></div>'+
+ '<div class="msg-header"><a href="/'+msg.user.uname+'/">@'+msg.user.uname+'</a>:</div>'+
+ '<div class="msg-ts"><a href="/'+msg.mid+'#'+msg.rid+'" title="'+msg.timestamp+' GMT">'+msg.timestamp+'</a></div>'+
+ '<div class="msg-txt">'+msg.body+'</div>'+
+ '<div class="msg-links"><a href="#" onclick="return showCommentForm('+msg.mid+','+msg.rid+')">Ответить</a></div>'+
+ '<div class="msg-comment" style="display: none"></div>'+
+ '</div>');
+
+ if(p) {
+ li.css('margin-left',parseInt(p.css('margin-left'))+20+'px');
+ p.after(li);
+ } else {
+ $('#replies').append(li);
+ }
+
+ updateRepliesCounter();
+}
+
+function onclickNewReply(e) {
+ var li=$(e);
+ li.removeClass('reply-new');
+ li.off('click');
+ li.off('mouseover');
+ updateRepliesCounter();
+}
+
+function onclickNextReply() {
+ var li=$('#replies>li.reply-new:first');
+ if(li.length) {
+ li.removeClass('reply-new');
+ li.off('click');
+ li.get(0).scrollIntoView();
+ updateRepliesCounter();
+ }
+}
+
+function updateRepliesCounter() {
+ var replies=$('#replies>li.reply-new').length;
+ if(replies>0) {
+ $('#wsthread').text(replies).css('display','block');
+ document.title='['+replies+'] '+pageTitle;
+ } else {
+ $('#wsthread').css('display','none');
+ document.title=pageTitle;
+ }
+}
+
+/******************************************************************************/
+/******************************************************************************/
+/******************************************************************************/
+
+function postformListener(formEl,ev) {
+ if(ev.ctrlKey && (ev.keyCode==10 || ev.keyCode==13)) {
+ if(!formEl.onsubmit || formEl.onsubmit()) {
+ formEl.submit();
+ }
+ }
+}
+
+function unfoldPostForm() {
+ if(window.location.pathname==="/" && window.location.hash==="#post") {
+ $('#newmessage>div').css('display','block');
+ $('#newmessage textarea').css('min-height','70px');
+ $('#newmessage textarea')[0].focus();
+ }
+}
+
+function onsubmitNewMessage() {
+ if($('#newmessage .tags').val().length==0) {
+ openDialog('<p class="dialogtxt">Пожалуйста, введите теги сообщения</p>');
+ return false;
+ } else if($('#newmessage textarea').val().length==0) {
+ openDialog('<p class="dialogtxt">Пожалуйста, введите текст сообщения</p>');
+ return false;
+ }
+ return true;
+}
+
+function showMoreReplies(id) {
+ $('#'+id+' .msg-comments').hide();
+
+ var replies=$('#replies>li');
+ var flagshow=0;
+ for(var i=0; i<replies.length; i++) {
+ if(flagshow==1) {
+ if(replies[i].style.display=="none") {
+ replies[i].style.display="block";
+ } else {
+ break;
+ }
+ }
+ if(replies[i].id==id) {
+ flagshow=1;
+ }
+ }
+ return false;
+}
+
+function showCommentForm(mid,rid) {
+ if($('#replies #'+rid+' textarea').length==0) {
+ var c=$('#replies #'+rid+' .msg-comment');
+ c.wrap('<form action="/comment" method="POST" enctype="multipart/form-data"/>');
+ c.before('<input type="hidden" name="mid" value="'+mid+'"/><input type="hidden" name="rid" value="'+rid+'"/>');
+ c.append('<div class="ta-wrapper"><textarea name="body" rows="1" class="reply narrow" placeholder="Add a comment..." onkeypress="postformListener(this.form,event)"></textarea><div class="attach-photo" onclick="attachCommentPhoto(this)"/></div><input type="submit" value="OK"/>');
+ }
+ // $('#replies #'+rid+' .msg-links').hide();
+ $('#replies #'+rid+' .msg-comment').show();
+ $('#replies #'+rid+' textarea')[0].focus();
+ $('#replies #'+rid+' textarea').autoResize({
+ extraSpace: 0,
+ minHeight: 1
+ });
+ return false;
+}
+
+function showCommentFooter(e) {
+ var a=$(e).closest("article");
+ if(a.find("footer.comm").length==0) {
+ a.append('<form action="/comment" method="POST" enctype="multipart/form-data"><input type="hidden" name="mid" value="'+a.data('mid')+'"/><footer class="comm"><div class="ta-wrapper"><textarea name="body" rows="1" class="reply narrow" placeholder="Написать комментарий..." onkeypress="postformListener(this.form,event)"></textarea><div class="attach-photo" onclick="attachCommentPhoto(this)"/></div><input type="submit" value="OK"/></footer></form>');
+ a.find('textarea').autoResize({
+ extraSpace: 0,
+ minHeight: 1
+ });
+ }
+ a.find('textarea')[0].focus();
+ return false;
+}
+
+function attachCommentPhoto(div) {
+ if($(div).children().length===0) {
+ var inp=$('<input type="file" name="attach" accept="image/jpeg,image/png" style="visibility: hidden"/>');
+ inp.on('change',function() {
+ $(this).parent().attr('class','attach-photo-active');
+ });
+ inp.trigger('click');
+ $(div).append(inp);
+ } else {
+ $(div).empty();
+ $(div).attr('class','attach-photo');
+ }
+}
+
+function attachMessagePhoto(div) {
+ var f=$(div).closest('form');
+ if(f.find('input:file').length===0) {
+ var inp=$('<input type="file" name="attach" accept="image/jpeg,image/png" style="float: left; width: 0; height: 0; visibility: hidden"/>');
+ inp.on('change',function() {
+ $(div).text("загрузить (✓)");
+ });
+ f.append(inp);
+ inp.trigger('click');
+ } else {
+ f.find('input:file').remove();
+ $(div).text("загрузить");
+ }
+}
+
+function unfoldReply() {
+ if((0+window.location.hash.substring(1))>0) {
+ var el=$(window.location.hash);
+ while(el.is(":hidden")) {
+ el=el.prev();
+ }
+ showMoreReplies(el.attr('id'));
+ window.location.replace(window.location.hash);
+ }
+}
+
+function showMessageLinksDialog(mid,rid) {
+ var hlink="http://juick.com/"+mid;
+ var mlink="#"+mid;
+ if(rid>0) {
+ hlink+="#"+rid;
+ mlink+="/"+rid;
+ }
+ var hlinkenc=encodeURIComponent(hlink);
+
+ var html="<div class=\"dialogshare\">Ссылка на сообщение:";
+ html+="<div onclick=\"$(this).selectText()\" class=\"dialogl\">"+hlink+"</div>";
+ html+="Номер сообщения:";
+ html+="<div onclick=\"$(this).selectText()\" class=\"dialogl\">"+mlink+"</div>";
+ html+="Поделиться:<ul>";
+ html+="<li><a href=\"https://www.facebook.com/sharer/sharer.php?u="+hlinkenc+"\" onclick=\"return openSocialWindow(this)\"></a></li>";
+ html+="<li><a href=\"https://twitter.com/intent/tweet?url="+hlinkenc+"\" onclick=\"return openSocialWindow(this)\" style=\"background-position: -32px 0;\"></a></li>";
+ html+="<li><a href=\"https://vk.com/share.php?url="+hlinkenc+"\" onclick=\"return openSocialWindow(this)\" style=\"background-position: -64px 0;\"></a></li>";
+ html+="<li><a href=\"https://plus.google.com/share?url="+hlinkenc+"\" onclick=\"return openSocialWindow(this)\" style=\"background-position: -96px 0;\"></a></li>";
+ html+="</ul></div>";
+
+ openDialog(html);
+}
+
+function showPhotoDialog(fname) {
+ var width=$(window).width();
+ var height=$(window).height()*0.9;
+ if(width<640) {
+ return true;
+ } else if(width<1280) {
+ openDialog("<a href=\"//i.juick.com/photos-1024/"+fname+"\"><img src=\"//i.juick.com/photos-512/"+fname+"\"/></a>");
+ $('#dialogw img').css('max-height',height+'px');
+ return false;
+ } else {
+ openDialog("<a href=\"//i.juick.com/p/"+fname+"\"><img src=\"//i.juick.com/photos-1024/"+fname+"\"/></a>");
+ $('#dialogw img').css('max-height',height+'px');
+ return false;
+ }
+}
+
+function openDialog(html) {
+ var dhtml="<table id=\"dialogt\"><tr><td><div id=\"dialogb\" onclick=\"closeDialog()\"></div><div id=\"dialogw\"><div id=\"dialogc\" onclick=\"closeDialog()\"></div>";
+ dhtml+=html;
+ dhtml+="</div></td></tr></table>";
+ $('body').append(dhtml);
+}
+
+function closeDialog() {
+ $('#dialogb').remove();
+ $('#dialogt').remove();
+}
+
+function openSocialWindow(a) {
+ var w=window.open(a.href,'juickshare','width=640,height=400');
+ if(window.focus) w.focus();
+ return false;
+}
+
+function checkUsername() {
+ var uname=$('#username').val();
+ $.ajax('http://api.juick.com/users?uname='+uname).done(function() {
+ $('#username').css('background','#FFCCCC');
+ }).fail(function() {
+ $('#username').css('background','#CCFFCC');
+ });
+}
+
+/******************************************************************************/
+
+function openDialogLogin() {
+ var html='<div class="dialoglogin"><p>Пожалуйста, представьтесь:'
+ +'<a href="/_fblogin" id="signfb">Facebook</a> '
+ +'<a href="/_vklogin" id="signvk">ВКонтакте</a></p>'
+ +'<p>Уже зарегистрированы?</p>'
+ +'<form action="/login" method="POST">'
+ +'<input class="signinput" type="text" name="username" placeholder="Имя пользователя"/><br/>'
+ +'<input class="signinput" type="password" name="password" placeholder="Пароль"/><br/>'
+ +'<input class="signsubmit" type="submit" value="OK"/>'
+ +'</form></div>';
+ openDialog(html);
+ return false;
+}
+
+/******************************************************************************/
+
+function likeMessage(e,mid) {
+ $.ajax({
+ url: 'http://juick.com/like?mid='+mid,
+ type: 'POST'
+ }).done(function() {
+ $(e).closest("article").append("<p>OK!</p>");
+ }).fail(function() {
+ $(e).closest("article").append("<p>Ошибка</p>");
+ });
+ return false;
+}
+
+/******************************************************************************/
+
+function setPopular(e,mid,popular) {
+ $.ajax('http://api.juick.com/messages/set_popular?mid='+mid+'&popular='+popular+'&hash='+hash).done(function() {
+ var a=$(e).closest("article");
+ a.append("<p>OK!</p>");
+ });
+ return false;
+}
+
+function setPrivacy(e,mid) {
+ $.ajax('http://api.juick.com/messages/set_privacy?mid='+mid+'&hash='+hash).done(function() {
+ var a=$(e).closest("article");
+ a.append("<p>OK!</p>");
+ });
+ return false;
+}
+
+/******************************************************************************/
+
+function readerLinkReplace(e) {
+ var a=$(e);
+ a.attr('href','/_out?lid='+a.data('lid'));
+}
+
+/******************************************************************************/
+
+jQuery.fn.selectText = function(){
+ var d = document;
+ if (d.body.createTextRange) {
+ var range = d.body.createTextRange();
+ range.moveToElementText(this[0]);
+ range.select();
+ } else if (window.getSelection) {
+ var selection = window.getSelection();
+ var range = d.createRange();
+ range.selectNodeContents(this[0]);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
+};
+
+/*
+ * jQuery.fn.autoResize 1.14
+ */
+
+(function($){
+
+ var uid = 'ar' + +new Date,
+
+ defaults = autoResize.defaults = {
+ onResize: function(){},
+ onBeforeResize: function(){
+ return 123
+ },
+ onAfterResize: function(){
+ return 555
+ },
+ animate: {
+ duration: 200,
+ complete: function(){}
+ },
+ extraSpace: 50,
+ minHeight: 'original',
+ maxHeight: 500,
+ minWidth: 'original',
+ maxWidth: 500
+ };
+
+ autoResize.cloneCSSProperties = [
+ 'lineHeight', 'textDecoration', 'letterSpacing',
+ 'fontSize', 'fontFamily', 'fontStyle', 'fontWeight',
+ 'textTransform', 'textAlign', 'direction', 'wordSpacing', 'fontSizeAdjust',
+ 'paddingTop', 'paddingLeft', 'paddingBottom', 'paddingRight', 'width'
+ ];
+
+ autoResize.cloneCSSValues = {
+ position: 'absolute',
+ top: -9999,
+ left: -9999,
+ opacity: 0,
+ overflow: 'hidden'
+ };
+
+ autoResize.resizableFilterSelector = [
+ 'textarea:not(textarea.' + uid + ')',
+ 'input:not(input[type])',
+ 'input[type=text]',
+ 'input[type=password]',
+ 'input[type=email]',
+ 'input[type=url]'
+ ].join(',');
+
+ autoResize.AutoResizer = AutoResizer;
+
+ $.fn.autoResize = autoResize;
+
+ function autoResize(config) {
+ this.filter(autoResize.resizableFilterSelector).each(function(){
+ new AutoResizer( $(this), config );
+ });
+ return this;
+ }
+
+ function AutoResizer(el, config) {
+
+ if (el.data('AutoResizer')) {
+ el.data('AutoResizer').destroy();
+ }
+
+ config = this.config = $.extend({}, autoResize.defaults, config);
+ this.el = el;
+
+ this.nodeName = el[0].nodeName.toLowerCase();
+
+ this.originalHeight = el.height();
+ this.previousScrollTop = null;
+
+ this.value = el.val();
+
+ if (config.maxWidth === 'original') config.maxWidth = el.width();
+ if (config.minWidth === 'original') config.minWidth = el.width();
+ if (config.maxHeight === 'original') config.maxHeight = el.height();
+ if (config.minHeight === 'original') config.minHeight = el.height();
+
+ if (this.nodeName === 'textarea') {
+ el.css({
+ resize: 'none',
+ overflowY: 'hidden'
+ });
+ }
+
+ el.data('AutoResizer', this);
+
+ // Make sure onAfterResize is called upon animation completion
+ config.animate.complete = (function(f){
+ return function() {
+ config.onAfterResize.call(el);
+ return f.apply(this, arguments);
+ };
+ }(config.animate.complete));
+
+ this.bind();
+
+ }
+
+ AutoResizer.prototype = {
+
+ bind: function() {
+
+ var check = $.proxy(function(){
+ this.check();
+ return true;
+ }, this);
+
+ this.unbind();
+
+ this.el
+ .bind('keyup.autoResize', check)
+ //.bind('keydown.autoResize', check)
+ .bind('change.autoResize', check)
+ .bind('paste.autoResize', function() {
+ setTimeout(function() {
+ check();
+ }, 0);
+ });
+
+ if (!this.el.is(':hidden')) {
+ this.check(null, true);
+ }
+
+ },
+
+ unbind: function() {
+ this.el.unbind('.autoResize');
+ },
+
+ createClone: function() {
+
+ var el = this.el,
+ clone = this.nodeName === 'textarea' ? el.clone() : $('<span/>');
+
+ this.clone = clone;
+
+ $.each(autoResize.cloneCSSProperties, function(i, p){
+ clone[0].style[p] = el.css(p);
+ });
+
+ clone
+ .removeAttr('name')
+ .removeAttr('id')
+ .addClass(uid)
+ .attr('tabIndex', -1)
+ .css(autoResize.cloneCSSValues);
+
+ if (this.nodeName === 'textarea') {
+ clone.height('auto');
+ } else {
+ clone.width('auto').css({
+ whiteSpace: 'nowrap'
+ });
+ }
+
+ },
+
+ check: function(e, immediate) {
+
+ if (!this.clone) {
+ this.createClone();
+ this.injectClone();
+ }
+
+ var config = this.config,
+ clone = this.clone,
+ el = this.el,
+ value = el.val();
+
+ // Do nothing if value hasn't changed
+ if (value === this.prevValue) {
+ return true;
+ }
+ this.prevValue = value;
+
+ if (this.nodeName === 'input') {
+
+ clone.text(value);
+
+ // Calculate new width + whether to change
+ var cloneWidth = clone.width(),
+ newWidth = (cloneWidth + config.extraSpace) >= config.minWidth ?
+ cloneWidth + config.extraSpace : config.minWidth,
+ currentWidth = el.width();
+
+ newWidth = Math.min(newWidth, config.maxWidth);
+
+ if (
+ (newWidth < currentWidth && newWidth >= config.minWidth) ||
+ (newWidth >= config.minWidth && newWidth <= config.maxWidth)
+ ) {
+
+ config.onBeforeResize.call(el);
+ config.onResize.call(el);
+
+ el.scrollLeft(0);
+
+ if (config.animate && !immediate) {
+ el.stop(1,1).animate({
+ width: newWidth
+ }, config.animate);
+ } else {
+ el.width(newWidth);
+ config.onAfterResize.call(el);
+ }
+
+ }
+
+ return;
+
+ }
+
+ // TEXTAREA
+
+ clone.width(el.width()).height(0).val(value).scrollTop(10000);
+
+ var scrollTop = clone[0].scrollTop;
+
+ // Don't do anything if scrollTop hasen't changed:
+ if (this.previousScrollTop === scrollTop) {
+ return;
+ }
+
+ this.previousScrollTop = scrollTop;
+
+ if (scrollTop + config.extraSpace >= config.maxHeight) {
+ el.css('overflowY', '');
+ scrollTop = config.maxHeight;
+ immediate = true;
+ } else if (scrollTop <= config.minHeight) {
+ scrollTop = config.minHeight;
+ } else {
+ el.css('overflowY', 'hidden');
+ scrollTop += config.extraSpace;
+ }
+
+ config.onBeforeResize.call(el);
+ config.onResize.call(el);
+
+ // Either animate or directly apply height:
+ if (config.animate && !immediate) {
+ el.stop(1,1).animate({
+ height: scrollTop
+ }, config.animate);
+ } else {
+ el.height(scrollTop);
+ config.onAfterResize.call(el);
+ }
+
+ },
+
+ destroy: function() {
+ this.unbind();
+ this.el.removeData('AutoResizer');
+ this.clone.remove();
+ delete this.el;
+ delete this.clone;
+ },
+
+ injectClone: function() {
+ (
+ autoResize.cloneContainer ||
+ (autoResize.cloneContainer = $('<arclones/>').appendTo('body'))
+ ).append(this.clone);
+ }
+
+ };
+
+})(jQuery);
+
+/******************************************************************************/
+
+$(document).ready(function() {
+ $('textarea').autoResize({
+ extraSpace: 0,
+ minHeight: 1
+ });
+
+ $('textarea.reply').click(function () {
+ $(this).addClass("narrow");
+ $(this).after('<div class="attach-photo" onclick="attachCommentPhoto(this)"/>');
+ $(this).parent().after('<input type="submit" value="OK"/>');
+ $(this).off('click');
+ });
+
+ $('textarea.replypm').click(function () {
+ $(this).addClass("narrowpm");
+ $(this).parent().after('<input type="submit" value="OK"/>');
+ $(this).off('click');
+ });
+
+ unfoldPostForm();
+ unfoldReply();
+ $(window).bind('hashchange',unfoldPostForm);
+ $(window).bind('hashchange',unfoldReply);
+
+ $(window).on('beforeunload',wsShutdown);
+});
diff --git a/src/main/webapp/style.css b/src/main/webapp/style.css
new file mode 100644
index 00000000..ea069c25
--- /dev/null
+++ b/src/main/webapp/style.css
@@ -0,0 +1,249 @@
+html,body,div,h1,h2,ul,li,p,form,input,textarea,pre { margin: 0; padding: 0; }
+html,input,textarea { font-family: sans-serif; font-size: 12pt; }
+html { background: #EEEEE5; color: #000; }
+body { width: 1024px; margin: 0 auto; }
+h1,h2 { font-weight: normal; }
+ul { list-style-type: none; }
+a { text-decoration: none; color: #069; }
+img,hr { border: none; }
+hr { height: 1px; background: #CCC; margin: 10px 0; }
+pre { white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word; }
+span.u { text-decoration: underline; }
+
+#content { width: 728px; margin: 15px 0 0 286px; }
+#topwrapper { position: relative; clear: both; }
+
+/********/
+
+body>header { width: 1024px; }
+body>header a { color: #000; border-bottom: 1px dotted #666; font-size: 13pt; }
+
+#logo { float: left; width: 110px; height: 36px; margin: 7px 25px 0 20px; }
+#logo a { display: block; width: 110px; height: 36px; text-indent: 100%; white-space: nowrap; overflow: hidden; border: 0; background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAG4AAAAkCAYAAABomA/xAAAAAXNSR0IArs4c6QAABPZJREFUaN7tml1sFUUUx3+UFhEROyBFURERFT+qrV8xxqgvSBBjSBMnii+aGKMmmBg1E60alYRk1JjwRIz4ojHq0qi88EI0EGN4QFopYERaxCraRMJYRFErvT50istme2dm9257Q/afNPfunbNztuc/Z87HLJQoUaJEiRIlStQRpuSdQKjoSWBdFZEdRsubJ/ofEyraDCyvIrLWaNlZkO52oNsh9qzR8o2sOhpq8Jw3Osa/mqRFeYNjfEeBug96yHydR8FpSZxQ0QKgZRKJO6euiRMqmglcUYce51pMvxgtDxWov9UxfshoeTiPgsY8NxstjwFT6y1wGy0/rkX8zoH2Ir2tVltliUkgLrPHCRU1AkPAjCpinxgtO6z8i8CrjmmvMVruTehZA7zguG+x0bLfyi8HNjvkHzNavpXQswx4CLgFOB84I9AkrxgtX7bf20KJEyo6G/gJmJUY+gvYAKwzWvblJs5o+a9Q0QCwpIpYPCX+2THlceDbtHjkuO8ocCBAD0BPzGAzgPeAjpxO8LedrxlYGEKcUFGDJSdJWhfwjNHyh5rGOGCOYzxO3GyH7B6j5YmU35tdRjBaVgL0nAB2x6431IC0k8R5eNvvQH+MtNnAu8CKuF8Ajxotu4rYKucBcwOIu9LXCxK4KnDbcenZZ7Q8HiuUH3DIvwN8CPw2jv3mAUut5/sQ1zu20ISKbgfeBy5M2KHDaHmwqKzyJo+UezAevzISd13gfSF67nXVekbLRzxssSkgMdklVDQVeAnoTMnKv3eRljervN7X22wi0xroOQgVTXfE0DTi2gPkXYvvswx2cXncEWCrJS6tlLpHqGhOkcS1+xJnjT+9iuwI0DtOIVttV/gH+CYR5K8NIK6mabtQ0TSPrboTuK3K+DRgVV14nIeB9hkt/8ywOPYaLYdj15c7ypOTZAgVtQDzM27f4+FqoMkh49MYeLgQ4mwmtCCAuLaMK7st0LAuogeMlkc8ZY8BfTXehbznESpqLcLjXN522Gg5kDHukOO+EKJdc/caLUdqHN8qwHqb/ufyuqzE1dqgaYlJaLwKfa6siykPcc8bLZ/wJO5Bm9RNKHHxjHI+IDJslUsc8aoC7MqxQGqdmEzxKF2+sJ9bAdfpQAtw90RvlfH4dqlDdtho+WvK70sd9/XZ04kxw13g0RDoiZUZix2yBwIySYBFKS2r5ELbDWA7RJ/m2S6DC3ChorOAywKIm+WQbRIqegr40pYFjTY7dL1WsDNx7TqDi8fdMz2yu9VCRcQ6Iqc8s+123AXsB14DXK9n7DdaxufqAlzF/QqhorlpCztL56TN4alDxHpxwKDHnG9meI49ies7fOWNlkao6EfgoiryK+2fC2MHyXcGPu/njPYkq4WRJuBxUk5VsmyVztiQaPr2pMQiH/Q7xgcT8WWlr7zF04w2nPOg22j5nVBRE+722Smea+vPTR46VgsViYkgrjvxgCPAMpsG9/N/F328ODBk938XEfHYeR9wSYA8RsuNwK2MNpAHgOEMtvjAfq4CznPILkz5baOHjnMZPcEIruKTMa6X6n3H+42WH1GiUDQkSGl2kHYx1bvvFWBbadbikUxOdgoVrbcx6WgsBjTYQP6cw0u3JY5yShRNnK1HFgGv55hvbWnSife4mTnmqQBrjJZbSpPWN3EV4A9G30raDrxttNxemrNEiRKnJ/4DSe9ztdhLwS4AAAAASUVORK5CYII=") no-repeat; }
+nav#global { float: left; }
+nav#global li { display: inline-block; margin: 14px 12px 0 0; }
+#search { float: right; margin: 12px 20px 12px 0; }
+#search input { background: #FFF; border: 1px solid #DDDDD5; padding: 4px; }
+
+#headdiv { clear: both; margin: 0 0 5px 0; padding: 0 20px; background: #DDDDD5; border-top: 1px solid #D5D5D0; border-bottom: 1px solid #D5D5D0; position: relative; }
+#headdiv li { display: inline-block; margin: 12px 12px 12px 0; }
+nav#actions { top: 0; right: 8px; position: absolute; }
+
+body>header nav li:after { display: inline-block; content: "/"; margin-left: 12px; color: #AAA; }
+body>header nav li:last-child:after { display: none; }
+
+body>header p { color: #000; font-size: 13pt; margin: 12px 0; text-align: center; }
+
+/********/
+
+#content>p, #content>h1, #content>h2 { margin: 1em 0; }
+
+#newmessage { background: #E5E5E0; padding: 15px; margin-bottom: 20px; }
+#newmessage textarea { border: 1px solid #CCC; padding: 4px; width: 688px; resize: vertical; min-height: 14pt; height: 14pt; margin: 0 0 5px 0; }
+#newmessage input { border: 1px solid #CCC; padding: 2px 4px; margin: 5px 0; }
+#newmessage>div { display: none; }
+#newmessage .img { width: 500px; }
+#newmessage .tags { width: 500px; }
+#newmessage .subm { width: 150px; background: #EEEEE5; }
+
+article { margin: 10px 0 20px 58px; background: #FFF; padding: 12px 13px; }
+article>aside { margin: -12px 0 0 -71px; width: 48px; height: 48px; float: left; }
+article>aside img { width: 48px; height: 48px; }
+article>header.u { overflow: hidden; display: inline-block; width: 460px; }
+article>header.t { width: 140px; text-align: right; float: right; }
+article time { color: #999; font-size: 10pt; border-bottom: 1px dotted #999; }
+article p { margin: 10px 0 15px 0; }
+article p.i { text-align: center; }
+article p.ir { float: right; margin-left: 10px; margin-bottom: 10px; }
+article p.ir a { cursor: -webkit-zoom-in; cursor: -moz-zoom-in; }
+article p.ir img { max-width: 200px; max-height: 200px; }
+article .irbr { clear: right; }
+article>nav.l { display: inline-block; font-size: 10pt; }
+article>nav.l a { color: #888; border-bottom: 1px dotted #AAA; margin-right: 15px; }
+article>nav.s { display: inline-block; text-align: right; float: right; }
+article>nav.s a { font-weight: bold; color: #222; }
+article a.likes { padding-left: 20px; background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAAXNSR0IArs4c6QAAAMRJREFUGBmtwbsuQ2EAAOCPX6LlKSRGD2E0YCKRGDqLQReDSVhIjI1r49bRdiZJn0GIQcPSxFO4HPHLOe1J2rpMvo//MaMl1batZNSWtlTLrK4JL5qqTqQO1aTqqppeTcrtexRklkTRskzw4Eju1rmOIIqCjrp7uRsNhQWLCmfu5C48Kxk05MmV3JR3xwatiaZ1rYg29Zr34VSPHdG6wpw3iaDPnmhDpiKVKPtmV7Sq4tOlET86EEUNw34RJK4Ffxgzrs8XpvA41+ECiwcAAAAASUVORK5CYII=") no-repeat 0px 1px; }
+article a.replies { margin-left: 18px; padding-left: 20px; background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAANdJREFUOMvN069OgmEUBvAfSCUxNicFKTQ3bkCLyRvwDrgHZ5Q7MFlobAY3G9UrsOi0iIGNBIGgG00o53PvCMAHBJ7thPecPc/5+3IoKKKDIeZrbIi74CiFQBs3eMTbmmRnuMUID5nzG90cFXcxyEqHU3zlEBigkQoUor9N8Recf4Gdpr8XgV+Uc/DK+Ekdz/hAcwNyE594Sp0NTJYOZobriB/FjUwjNkE9m36GKq5wHO9zXOICtch4j1f0Q2QlSnjBGL10dXlQxXvSVmWbTZ3E/5ijtRxcAIj4MflVC0WJAAAAAElFTkSuQmCC") no-repeat 0px 1px; }
+article footer.comm { margin: 13px 0 0 0; }
+article textarea { width: 530px; padding: 2px; resize: vertical; vertical-align: top; min-height: 12pt; height: 12pt; border: 0; }
+article input { width: 50px; margin-left: 6px; vertical-align: top; border: 1px solid #CCC; background: #EEE; color: #999; }
+
+#yandex_ad_728 { width: 728px; height: 90px; margin: 20px 0; padding: 15px 0; background: #FFF; }
+.adsbygoogle { display: inline-block; width: 728px; height: 90px; margin: 10px 0 20px 0; }
+
+#geomap { width: 700px; height: 300px; margin-top: 1em; overflow: hidden; }
+
+.msg { margin: 10px 0 20px 0; }
+.ads { padding: 13px 10px 5px 10px; margin: 8px 0 16px 58px; background: #FFF; }
+.msgthread { margin-bottom: 0; }
+.msg-avatar { float: left; width: 48px; height: 48px; }
+.msg-avatar img { width: 48px; height: 48px; vertical-align: top; }
+.msg-cont { background: #FFF; margin-left: 58px; padding: 12px 15px; width: 640px; }
+.msg-menu { float: right; width: 16px; height: 16px; }
+.msg-menu>a { display: block; width: 16px; height: 16px; vertical-align: top; background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAAXNSR0IArs4c6QAAALRQTFRFAAAAldX/ncT/ksj/ltL/nMb/lMn/mcz/l9H/lcr/mZmZmcz/m83/mJiYmZmZmcz/mM7/mpqamsr/l8v/m83/mJiYmM7/l8v/mZmZmcz/mpqamMv/mcz/mM3/ms3/msv/mMv/mMz/mcz/msz/mc3/mMz/mcz/msz/mc3/mcv/mc3/mcv/mcz/mcz/mcz/mcz/mZmZmZmZmcz/mcz/mcz/mcz/mcz/mcz/mcz/mcz/mcz/mZmZiZsGCAAAADx0Uk5TAAwNDhESExQWGC0tLi8yMjQ1NTY4OTk7PDxESktNUVNUd3h5eoGCg4mKmMvMzc7P29zd3uLj7O3u8vT2+A5wCAAAAJFJREFUGBkFwQlCgkAAAMDRxEySkryyzDxSAc+gkvX//2oGAAB42J5COG46AJ+/+SiKxvl1DsyqJyCtZnBXxgDxdxPrHQDZCvvUNIFkalAgtCSXHr1LIqpRt+ifu91zn6hG8YLh7TbEoMAyg8kEsgWaZQzwWDbgvXoG0p83YH7Nx+32a/73AdDZHEIovu4BAMA/t6QMuyHliCkAAAAASUVORK5CYII=") no-repeat; }
+.msg-header { overflow: hidden; }
+.msg-ts { font-size: small; vertical-align: top; margin: 5px 0; }
+.msg-ts, .msg-ts>a { color: #999; }
+.msg-place { font-size: small; }
+.msg-place>a { color: #999; }
+.msg-txt { overflow: hidden; margin: 10px 0 12px 0; }
+.msg-media { text-align: center; }
+.msg-links { font-size: small; color: #999; margin: 5px 0 0 0; }
+.msg-comments { overflow: hidden; font-size: small; color: #AAA; text-indent: 10px; margin-top: 10px; }
+.msg-comment { margin: 5px 0; }
+.ta-wrapper { display: inline-block; border: 1px solid #DDD; }
+.msg-comment textarea { width: 634px; padding: 2px; resize: vertical; vertical-align: top; min-height: 12pt; height: 12pt; border: 0; }
+.msg-comment .narrow { width: 554px; }
+.msg-comment .narrowpm { width: 580px; }
+.attach-photo { display: inline-block; padding: 2px 4px; cursor: pointer; width: 16px; height: 13px; overflow: hidden; background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAANCAQAAAAKsiavAAAAAXNSR0IArs4c6QAAAKRJREFUGNNjZICC6Q5M+xlQwD/HzAMMDEwwLnM9AxqAiDDO/M+AFzCh8B4zhrDwsfAxBjHcggkhm/CY2SDlHYQ5TZD5EoMMmgmMhSnvZnrPfDLzyQyvrPf/CzGs+L2TgYFhBoM0gzTjDAaGX7uwuQG/I1ldGRj+pzM8YXjMmMrAwOGK6cibfy2z3sMdeYpBBTMcHjMU/9zJzsjo8r8DIk1yQGEBAFzpL+AuTCqZAAAAAElFTkSuQmCC") no-repeat 3px 4px; }
+.attach-photo-active { display: inline-block; padding: 2px 4px; cursor: pointer; width: 16px; height: 13px; overflow: hidden; background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAANCAMAAACXZR4WAAAAAXNSR0IArs4c6QAAAEVQTFRFAAAAAJUAAJ0AAJIAAJkAAJYAAJwAAJcAAJoAAJgAAJkAAJoAAJoAAJgAAJkAAJoAAJkAAJkAAJkAAJkAAJkAAJkAAJkA9z3GXQAAABZ0Uk5TAAwNDg8REkBHSktRU1RVv9jZ2+Lj5OeV7PgAAABSSURBVAhbdcjBFkAgFADRIUlE9Or9/6daUC0cs5pzAXD65niKFSJQv/aChHHcrg4yA9jcILCW4tkbDGRVwfzDhs+yEBqcFsCmBiqHmULSDr0P3JdgDbuscEckAAAAAElFTkSuQmCC") no-repeat 3px 4px; }
+.msg-comment input { width: 50px; margin-left: 6px; vertical-align: top; border: 1px solid #CCC; background: #EEE; color: #999; }
+.msg-recomms { margin-top: 10px; overflow: hidden; font-size: small; color: #AAA; text-indent: 10px; }
+.reply-new .msg-cont { border-right: 5px solid #0C0; }
+blockquote { border-left: 1px dashed #CCC; margin: 10px 0 10px 10px; padding-left: 10px; }
+
+#mtoolbar { width: 670px; margin-left: 58px; background: #E5E5DD; border-top: 1px solid #CCC; }
+#mtoolbar ul, #mtoolbar a { padding: 5px; }
+#mtoolbar li { display: inline; }
+#mtoolbar div { display: inline-block; width: 16px; height: 16px; background: url(//static.juick.com/toolbar-icons.png) no-repeat; vertical-align: middle; margin: 5px; }
+
+.newmessage { width: 695px; padding: 2px; resize: vertical; border: 1px solid #DDD; } /* textarea */
+
+.users { width: 100%; margin: 10px 0; } /* table */
+.users td { width: 33%; padding: 6px 0; overflow: hidden; } /* table */
+.users img { width: 32px; height: 32px; vertical-align: middle; margin-right: 6px; } /* table */
+
+.title2 { padding: 10px 20px; margin: 20px 0; background: #DDDDD0; }
+.title2-right { float: right; line-height: 24px; }
+#content .title2 h2 { font-size: x-large; margin: 0; }
+
+.page { text-align: center; padding: 5px; background: #E5E5DD; }
+
+/* signup form */
+.signup-h1>img { vertical-align: middle; margin-right: 10px; }
+.signup-h1 { margin: 20px 0 10px 0; font-size: x-large; }
+.signup-h2 { font-size: large; margin: 10px 0 5px 0; }
+.signup-hr { margin: 20px 0; }
+
+/********/
+
+#readerlinks li { margin: 15px 0; }
+#readerlinks img { vertical-align: top; margin: 1px 7px 0 0; }
+#readerlinks a { color: #000; border-bottom: 1px dotted #666; }
+#readerlinks a:visited { color: #999; }
+
+/********/
+
+.newpm { margin: 20px 60px 30px 60px; }
+.newpm textarea { width: 100%; resize: vertical; }
+.newpm-send input { width: 100px; }
+
+/********/
+
+#column { width: 240px; padding-top: 10px; overflow: hidden; float: left; margin-left: 10px; }
+#column ul, #column p, #column hr { margin: 10px 0; }
+#column li { margin: 6px 0; }
+#column .margtop { margin-top: 15px; }
+#column p { font-size: 10pt; line-height: 140%; }
+#column .tags { text-align: justify; }
+#column .inp { width: 222px; padding: 3px; border: 1px solid #CCC; border-radius: 3px; background: #F5F5E9; }
+#ctitle { font-size: 14pt; }
+#ctitle img { vertical-align: middle; margin-right: 5px; }
+#ctoolbar { margin: 10px 0; padding: 5px; line-height: 0; background: #E5E5DD; }
+#ctoolbar li { display: inline; }
+#ctoolbar a { padding: 5px 10px;}
+#ctoolbar div { display: inline-block; width: 16px; height: 16px; background: url(//static.juick.com/toolbar-icons.png) no-repeat; vertical-align: middle; margin: 5px 0; }
+#ustats li { margin: 3px 0; font-size: 10pt; }
+#column table.iread { width: 100%; }
+#column table.iread td { text-align: center; }
+#column table.iread img { width: 48px; height: 48px; }
+
+/********/
+
+#dialogb { position: fixed; top: 0; left: 0; width: 100%; height: 100%; opacity: 0.6; background: #000; z-index: 10; }
+#dialogt { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 10; }
+#dialogt td { vertical-align: middle; text-align: center; }
+#dialogw { position: relative; display: inline-block; text-align: left; z-index: 11; }
+#dialogc { position: absolute; top: -15px; right: -15px; width: 30px; height: 30px; z-index: 12; cursor: pointer; background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAQAAACROWYpAAAEEElEQVQ4y32Va2yTVRzGf23fd10vUNqxsY1dGIyba7BsTgiwhcRkgxHkFkJAuQzIggY0IXGYkKA4DeAQMQTIiIB+MyRAJn7hAxJRJKIRiJgQIhESZxYFEuja9f74oe1aoPh/vrx5z3nO/3qeA0+aBSvW+UaT2VBUb+91HXY3FDUUNZnzDaxYsfBcs2DDpHjqqC1ln07YU79t0oGX+mY1V3dWv1/VWVrqphgTW+EDLBjYS91tZZ/5v+34d1/sijIWv/bw2IXFy2urfKVu7BjP0q0YOCaMWVlzfF7wdJY2mLybzH4/+mpq3cxKrwcHBtanvTqrfK/X97+avCvFUn2xtjDBNJpDn8SGU1Ls+oXVTTUeL8587xYMHKN97fWnlikqnU/UDmWJWUwL/ZKQpPOvTZvg8eLI0W3Y8cyo+3hB4p50MmYLUgC24EdRSTq6rLIaD/Z06SyYuMdWvjnv4TfS+URhahpHYlLwR16gHDcmVrBSPNrXOv2LTimUrMoE3BiaO5Jz2dCmiDNIkGBRcCAuffde1WR8FGMDKy5n5cp5f34pHYqlt/tDkrQ7SpDgxNBASurLrHRFpPsXmUEFbgyw4fFM7FgQvi7NznirHQorTZ8cGkhJUm80vVIylJA0ppU6xlAEBiXj/EvWSI+TuewWhdP0xylJOhPPVeJGVNqxbdQ0xlIMprusoXnzW9LtRH5xFoUjmfHIpxL8Oiod3ed6kXE4wHRW+Od0dku/J57s7P1UmvxBNP//2ah0+CCNVOICk8qyOS3bpAeJfOpgSpISypUujZ+GpXd6HC8zPkM2Z9Vtiv0jZRvlGRrM5Lo4k/vWSHZUHsWlGW+YI+QKs7lh/Z1L0tuZLbPDuVzTpTsVT6+0haXB31hNUzbsMjMwZdWBz6XBuDOY7efWiG3kYuyOZmO6FJLOnWM5mYIZlDDd3lHVPXAt18/C2DAsBf8u2cECpqdbZcNDndHi27S5T5I2DD+POjccT0qHz9JJS3ZIrLioIGBbNu7dI6claVdB72uHI0np8lW6WUIgO55WivFRT6ttvffDQ/2SdCuyatiZR2wLXwlL0uVf6WEdrdRnL4YFEzfl+Gm3dXn2bun/67YkRZI3oscjxyM/Rx7FJenBwP6L7KWLdvy5K5kRA6qZSYety9nDiYM//HFLeXbvzsmr5kl66KKDmeSJQUaG8FJDgHbW0e3YzwnPmVe+33lz582Oy+P7OcF+ullHOwFqeEKGMgKIl2r8tLCUjWxnl7HH6KXX2GPdxXY2spQW/FTzlACOSC8eyplEgBYWsoI1rGUta1jBQloIMIlyCkhv9r2w48JLObVMwU+ARhoJ4GcKtZTjxZXL9Vm6BSsGJnYcOHFl4MSBHRMDK5b/e69yxzyNAvYf9TCL+HAwka4AAAAASUVORK5CYII="); }
+
+.dialoglogin { width: 300px; background: #EEEEE5; padding: 25px; }
+#signfb,#signvk { display: block; width: 100%; height: 32px; line-height: 32px; text-indent: 37px; text-decoration: none; overflow: hidden; margin: 10px 0; }
+#signfb { color: #FFF; background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAADNQTFRFO1edX3ewl6bLnKrOoK3QrrrYvMXe2N7r3OLu3+Tv5urz7O/29vf6+Pn7+vv9/Pz9////ykQjsQAAAEZJREFUOMtjYBgFuAATO68ADxdOaUYuATDAqYBbAL8CFgECCjiBcqz4XMiPz3oQEKCtAgEkwEdIAQchBWyEFDAPkDdHsAIAhZkIwz/VK/UAAAAASUVORK5CYII=") no-repeat #3A569C; }
+#signvk { margin-bottom: 30px; color: #FFF; background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAAHJQTFRFbY+zbo+zbpCzb5C0cpO1c5O2dZW3dpa4e5m6gJ29gZ69lq/In7bNo7jPrcDUs8XXvs3dv87dy9fkztnlz9rm0Nrm093o1N7o1+Dq3OTt3ubu4Ofv5Orw7fH27vL28PP38vX49Pb5+vv8+/z9/Pz9////2jSYlQAAAG5JREFUOMvtkEcOgDAMBE3vvXdIyP+/iMMRKfYHmMtcRtE6AD8f1Is8pyKgAs0RGYO2HSWqMQaoBHVRgYsS3AsrtyFlrqgdJlCLb95gxQO6IkZCqL+KCjz0TQU5ejOf2a3aJXPF7BOB2PvMhp8PDzGRFgEe7xvEAAAAAElFTkSuQmCC") no-repeat #6d8fb3; }
+.dialoglogin form { margin-top: 7px; }
+.signinput,.signsubmit { border: 1px solid #CCC; margin: 3px 0; padding: 3px; }
+.signinput { width: 292px; }
+.signsubmit { width: 70px; }
+
+.dialogshare { padding: 20px; background: #EEEEE5; border: 1px solid #999; min-width: 300px; overflow: auto; }
+.dialogl { padding: 5px; margin: 3px 0 20px; border: 1px solid #DDD; background: #F5F5E9; }
+.dialogshare li { float: left; margin: 5px 10px 0 0; }
+.dialogshare a { display: block; width: 32px; height: 32px; background-image: url(//static.juick.com/sharesocial.png); }
+
+.dialogtxt { background: #EEEEE5; padding: 20px; }
+
+/********/
+
+#wsthread { position: fixed; bottom: 20px; right: 20px; background: #CCC; cursor: pointer; padding: 5px 10px; display: none; }
+
+/********/
+
+#footer { clear: both; font-size: 10pt; padding: 10px 0; color: #999; width: 1004px; margin: 0 auto 20px 0; }
+#footer-social { float: left; }
+#footer-social a { display: inline-block; width: 32px; height: 32px; text-indent: 100%; white-space: nowrap; overflow: hidden; border: 0; margin: 0 15px 0 0; }
+#footer-left { margin-left: 286px; margin-right: 350px; }
+#footer-right { float: right; }
+
+/******************************************************************************/
+
+.sharenew { display: inline-block; padding: 0 12px 0 37px; min-height: 32px; line-height: 32px; min-width: 200px; color: #FFF; }
+
+.ico32-twi { background: #55acee url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAAM9QTFRFVazuVqzuV6zuWK3uWq7uXK/uXrDuYLHvY7PvZLPvZrTvZ7Tva7bwbLfwbbjwbrjwcbnwc7rxdbvxer7xfL/xf8Dyg8LyhcPyhsTyh8TzisXzi8bzjcfzjsfzksn0qNT1rNb2udz3v+D4weD4weH4w+H5w+L5yuT5yuX5y+X5zuf60ej51er61uv62uz62+374O/75fL76PP86vT87PX87vb87/f98Pf88Pf98fj99Pn99vr99/v++Pv9+vz9+/z9/P39/f39/f7+/v7+////bJnt2AAAAKlJREFUGBntwUdWAkEABNAaQJIOkpMSJCcDKEEJ2lTd/0wyD1bdbFiw43/g5kIJHJXf/2b1u2fY/IWPQO1botiBraBFNQqE2zIy3L/CFl9Tg6dUbiyjAx+2WE8kl29zBfZwZD5+xYDRwTQEW2RCUkfkiwdHerilTpiHK9zXCTWKwOWVVjSUZPiTxVnJJimRu6oH1/1DpbURjfRZDOGMx+7WkNRXI4Gba/gHx4AiZYtLseAAAAAASUVORK5CYII=") no-repeat; }
+.ico32-vk { background: #6d8fb3 url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAAHJQTFRFbY+zbo+zbpCzb5C0cpO1c5O2dZW3dpa4e5m6gJ29gZ69lq/In7bNo7jPrcDUs8XXvs3dv87dy9fkztnlz9rm0Nrm093o1N7o1+Dq3OTt3ubu4Ofv5Orw7fH27vL28PP38vX49Pb5+vv8+/z9/Pz9////2jSYlQAAAG5JREFUOMvtkEcOgDAMBE3vvXdIyP+/iMMRKfYHmMtcRtE6AD8f1Is8pyKgAs0RGYO2HSWqMQaoBHVRgYsS3AsrtyFlrqgdJlCLb95gxQO6IkZCqL+KCjz0TQU5ejOf2a3aJXPF7BOB2PvMhp8PDzGRFgEe7xvEAAAAAElFTkSuQmCC") no-repeat; }
+.ico32-fb { background: #3b579d url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAADNQTFRFO1edX3ewl6bLnKrOoK3QrrrYvMXe2N7r3OLu3+Tv5urz7O/29vf6+Pn7+vv9/Pz9////ykQjsQAAAEZJREFUOMtjYBgFuAATO68ADxdOaUYuATDAqYBbAL8CFgECCjiBcqz4XMiPz3oQEKCtAgEkwEdIAQchBWyEFDAPkDdHsAIAhZkIwz/VK/UAAAAASUVORK5CYII=") no-repeat; }
+.ico32-lj { background: #888888 url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAAlJQTFRFAAAAAAAAAFVVADNmACRtAEBgADNmADdtADBgADljADNmADFiADFjADVpADJjADRpADRoADFnADNmADJkADVlADJnADRnADJlADJnADJlADRnADNmADJnADNmADJmADRmADNlADRmADNnADNmADJmADRmADNmADRmADJmADNlADNmADNmADJmADNnADNlADNnADNmADNlADNmADNmADNmBTRmADNmADNmADNmWGh4ADNmBDZnK1J6LWCURmiIRmmLc4mfADNmKlyPADNmGkNsLF+SKFiIR3qtL1Z+ATRnDT9xE0Z5Gk2BN2ueOVh1UIO2DkFzE0Z5JVeLK1J5M2WYPlp1T3ehJEpuADNmK16QPG+iQHOmRFxze35/ADNmLV+SjIeCMVl/TmJ4BThqBzZnR3mtBjpsWo7AZ4KeFENxH0h0bJ7SADNmAzZpHz9sAzdqCzppEkBvF0RyO1+EP3OmBDZodKbafWR+ATRoDjlpQ3epADNmATNlDDdoQWOGaJvOBDVmBDZoQWWKRnmrUoS3AzZqBzdoWFZ4cKTVc4mgADNmBjdmCzhoVom5W47CADNmADNnAzRnBjVoWlh5opSHADNmATRlATRmCDlqcaXYh7rtADNmWYy/XI/CXpLFYZTHY5bJZZjLZpjLZpnMb2B9cmB8hbjrhp2zibvvjL7ylMj6l8r9l8v9mY6Fmcz/qneIrba+snqKuaKLwKeMxMbIx8jJy8vLzMzM1LOQ1dXV1d3m2YqR4LmS4uju7sGW78KV8cSX+fn5+smY/cqZ/f39/8yZQ9icRAAAAJt0Uk5TAAEDBQcICg4QEhkaHyIkJyw0QUJESE9RUlZeX2Fpa2x5e4GHiYqMmZ2rr7S2uL/Cw8nNzs/R19/g4OPj4+fn5+fo6Onp6erq6+zs7Ozs7Ozt7e3t7e3t7u/v7+/v7/Dw8PHx8vLy8/Pz9PT09fX19vb29vb29/f3+Pj4+fn5+fn6+vr6+vv7+/v7/Pz8/Pz9/f39/f3+/v7+/v7uuPuRAAABVElEQVQ4y83QU3sDURCA4U2dOrVtpbZt27Ztt2lTY6tTnxpb2/pf3dxPcp3vdt5n5jyHIIQpKSOXZS91/nPN4p6FvckuFr+5Hrn/wpmvbHBnwnMWt+1qdPD563LAhgECu9qLufGn37fDRmQIgm5Oh//DXflKaFkyVxkCDqT3w23ByCpFnUQFyQBAsTr8vjeyM46iqIM6e1FAaKGmlmkc00qLPtIMOmKOMjDeDTymRRapAQBxx6INjDd9zijqNDpXHhBy6Qk7GA+H0SuOSp0lAKFG5mGMM6toMTZrAT3DhByiRcgaLUqQDgBE2DXrGG9V+KX6FgazoRXSAfHbGMe+P378eXiCP67ETcQ45Y8uf0YMFAaoeSqHB66RAggY1u1JnzzwilRBQDDdlrJvaPDTrwsDQlLfFaUtfp9PmBL8YqhYcesRsiQEJGscgZwIgYlp2xJC0z8XJH2ZPhmkKgAAAABJRU5ErkJggg==") no-repeat; }
+.ico32-gp { background: #dc4a38 url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAAXNSR0IArs4c6QAAAa9JREFUSMftlTFoU1EUhr/3EguZFFzSIVVehyq03dxEJy3iIiJCi4Nr6eQmLoKggg46CaKj6FBB0OIgXcRJiQiKiFIf2KEJVsSCTWlN+Bx8eaQtaa5Dt57tP+/+/33n3P/cCzuxPeFh7/nRFdvxxtFw8m4f+tojJl62qT4xsRBOj511yT0Zuqjq5P/8/Lj6LEd9LqoLRt0Z8QY8Dqy2QbTGI6CfSrjAADDYgd8BUAgXWABG3Z/jH8Ai81lJE97pJfAYiJnK8RBwJWplaISTvZpYcEZteh7AQ9a90fH1ut96n0Ofl0zVOT/Y8mZurapVa65atert7iUQrUXXooQS9xkm5m6WbpCS8osmKSn1EEdcVfVWpweCSsgXH7Ch6rSlrQSKm9vICY4xxF5qVNjFGcqORQ0AXrLUa98x55x30oPG4LCfVDubtjX9tE2/tEcpm81XasNiCL1oTT23Idvvb7USYuURypmZO4+1xnv+8DNE4N8UHt20Zh9Po+WwHrxVGx5fl7tg3YHunHVXhYO8IEFmeE7KCglnKTMRfQ63T8kpZ/1uy2W/+sBTxjuPzLbHX6Ju9rcCQGEmAAAAAElFTkSuQmCC") no-repeat; }
+
+/******************************************************************************/
+
+@media screen and (max-width: 850px) {
+ body,body>header,#topwrapper,#content,#footer,#mtoolbar { width: auto; min-width: 310px; margin: 0 auto; }
+
+ body>header { margin-bottom: 15px; }
+ body>header a { font-size: 12pt; }
+ #logo { display: none; }
+ nav#global { margin-left: 10px; }
+ nav#global li { margin-right: 10px; }
+ #search { float: none; display: inline-block; margin: 10px 10px; }
+ #headdiv { padding: 0 10px; }
+ #headdiv li { margin-right: 10px; }
+ nav#actions { position: relative; right: auto; }
+
+ #column { float: none; padding-top: 0; width: auto; margin: 0 10px; }
+
+ article { margin-left: 0; overflow: auto; }
+ article>aside { margin: 0 10px 0 0; width: 40px; height: 40px; }
+ article>aside img { width: 40px; height: 40px; }
+ article>header.u { margin: 0; width: auto; display: block; }
+ article>header.t { float: none; text-align: left; margin: 0; width: auto; display: block; }
+ article p { margin: 10px 0 8px 0; }
+ article>nav.l { display: block; float: left; width: 65%; line-height: 15pt; }
+ article>nav.s { display: block; }
+ article textarea { width: 205px; }
+ article footer { float: left; }
+
+ #content textarea { width: 100%; }
+
+ .msg,.msg-cont { width: auto; min-width: 280px; }
+ .msg-cont,.ads { margin-left: 0; }
+ .msg-avatar { margin: 10px 10px 0 10px; width: 40px; height: 40px; }
+ .msg-avatar img { width: 40px; height: 40px; }
+ .msg-comment textarea { width: 100%; }
+ .msg-txt { margin: 8px 0 0 0; }
+ .msg-media { overflow: auto; }
+
+ .title2 h2 { font-size: large; }
+
+ #footer { margin: 0 10px; }
+ #footer div { float: none; margin: 10px 0; }
+}