From af80956ec669cf48ea6423959b5a4d8f16385d6f Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Sat, 24 Oct 2015 19:35:41 +0300 Subject: moving to Gradle --- src/main/java/com/juick/Message.java | 171 ++++++ src/main/java/com/juick/Tag.java | 35 ++ src/main/java/com/juick/User.java | 53 ++ src/main/java/com/juick/http/www/Discover.java | 120 ++++ src/main/java/com/juick/http/www/Errors.java | 44 ++ .../java/com/juick/http/www/FacebookLogin.java | 187 ++++++ src/main/java/com/juick/http/www/Help.java | 94 +++ src/main/java/com/juick/http/www/Home.java | 163 +++++ src/main/java/com/juick/http/www/Login.java | 259 ++++++++ src/main/java/com/juick/http/www/Main.java | 322 ++++++++++ src/main/java/com/juick/http/www/NewMessage.java | 420 +++++++++++++ src/main/java/com/juick/http/www/PM.java | 231 +++++++ .../java/com/juick/http/www/PageTemplates.java | 584 ++++++++++++++++++ src/main/java/com/juick/http/www/RSS.java | 113 ++++ src/main/java/com/juick/http/www/Settings.java | 104 ++++ src/main/java/com/juick/http/www/SignUp.java | 331 ++++++++++ src/main/java/com/juick/http/www/User.java | 413 +++++++++++++ src/main/java/com/juick/http/www/UserThread.java | 370 +++++++++++ src/main/java/com/juick/http/www/Utils.java | 244 ++++++++ .../java/com/juick/http/www/VKontakteLogin.java | 155 +++++ src/main/java/ru/sape/Sape.java | 25 + src/main/java/ru/sape/SapeConnection.java | 107 ++++ src/main/java/ru/sape/SapePageLinks.java | 95 +++ src/main/java/ru/sape/SerializedPhpParser.java | 221 +++++++ src/main/webapp/WEB-INF/web.xml | 16 + src/main/webapp/favicon.png | Bin 0 -> 244 bytes src/main/webapp/logo.png | Bin 0 -> 1184 bytes src/main/webapp/scripts.js | 684 +++++++++++++++++++++ src/main/webapp/style.css | 249 ++++++++ 29 files changed, 5810 insertions(+) create mode 100644 src/main/java/com/juick/Message.java create mode 100644 src/main/java/com/juick/Tag.java create mode 100644 src/main/java/com/juick/User.java create mode 100644 src/main/java/com/juick/http/www/Discover.java create mode 100644 src/main/java/com/juick/http/www/Errors.java create mode 100644 src/main/java/com/juick/http/www/FacebookLogin.java create mode 100644 src/main/java/com/juick/http/www/Help.java create mode 100644 src/main/java/com/juick/http/www/Home.java create mode 100644 src/main/java/com/juick/http/www/Login.java create mode 100644 src/main/java/com/juick/http/www/Main.java create mode 100644 src/main/java/com/juick/http/www/NewMessage.java create mode 100644 src/main/java/com/juick/http/www/PM.java create mode 100644 src/main/java/com/juick/http/www/PageTemplates.java create mode 100644 src/main/java/com/juick/http/www/RSS.java create mode 100644 src/main/java/com/juick/http/www/Settings.java create mode 100644 src/main/java/com/juick/http/www/SignUp.java create mode 100644 src/main/java/com/juick/http/www/User.java create mode 100644 src/main/java/com/juick/http/www/UserThread.java create mode 100644 src/main/java/com/juick/http/www/Utils.java create mode 100644 src/main/java/com/juick/http/www/VKontakteLogin.java create mode 100644 src/main/java/ru/sape/Sape.java create mode 100644 src/main/java/ru/sape/SapeConnection.java create mode 100644 src/main/java/ru/sape/SapePageLinks.java create mode 100644 src/main/java/ru/sape/SerializedPhpParser.java create mode 100644 src/main/webapp/WEB-INF/web.xml create mode 100644 src/main/webapp/favicon.png create mode 100644 src/main/webapp/logo.png create mode 100644 src/main/webapp/scripts.js create mode 100644 src/main/webapp/style.css (limited to 'src/main') 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 . + */ +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 Tags = new ArrayList(); + 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 childs = new ArrayList(); + + 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 . + */ +package com.juick; + +/** + * + * @author Ugnich Anton + */ +public class Tag implements Comparable { + + 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 . + */ +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 . + */ +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 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 = ""; + } else if (paramBefore > 0 || mids.size() < 5) { + head = ""; + } + PageTemplates.pageHead(out, title, head); + PageTemplates.pageNavigation(out, visitor, null); + PageTemplates.pageHomeColumn(out, sql, visitor); + + out.println("
"); + + 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("

Читать дальше →

"); + } + + out.println("
"); + + 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("
"); + out.println("

Страница не найдена

"); + out.println("

Сожалеем, но страницу с этим адресом удалил её автор, либо её никогда не существовало.

"); + out.println("
"); + + 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 . + */ +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 . + */ +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(""); + + out.println("
"); + printFile(out, f); + out.println("
"); + + 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 . + */ +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 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 = ""; + } + 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("
"); + + if (paramShow == null && paramBefore == 0) { + out.println(""); + } + + if (visitor != null) { + out.println("
"); + out.println("
"); + out.println(" "); + out.println("
"); + out.println(" или загрузить
"); + out.println("
"); + out.println(" "); + out.println("
"); + out.println("
"); + out.println("
"); + } + + 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 += "&show=" + paramShow; + } + if (paramSearch != null) { + nextpage += "&search=" + URLEncoder.encode(paramSearch, "UTF-8"); + } + + out.println("

Читать дальше →

"); + } + + if (paramShow == null && paramBefore == 0) { + out.println(""); + } + + out.println("
"); + + 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 . + */ +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(""); + out.println(""); + out.println(""); + out.println("Juick"); + out.println(""); + out.println(""); + out.println(""); + out.println(""); + + out.println(""); + + out.println(""); + + out.println("
juick.com © 2008-2014   Контакты · Помощь
"); + + out.println("
"); + out.println(" Зарегистрироваться:"); + out.println(" "); + out.println(" "); + out.println("
XMPP"); + out.println("
Отправьте LOGIN на juick@juick.com
"); + out.println("
"); + out.println("
"); + out.println("
Уже зарегистрированы?"); + out.println("
"); + out.println(""); + out.println(""); + out.println(""); + out.println("
"); + out.println("
"); + + out.println(""); + out.println(""); + } 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 . + */ +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 GET 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 POST 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 . + */ +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, "Написать", "" + + "" + + "" + + ""); + PageTemplates.pageNavigation(out, visitor, null); + + out.println("
"); + out.println("
"); + out.println("

Место: Отменить

"); + out.println("

Фото: (JPG, PNG, до 10Мб)

"); + + 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("


"); + + out.println("" + "" + "

"); + out.println("
"); + out.println("
"); + out.println("

Теги:

"); + printUserTags(sql, out, visitor); + out.println("
"); + + PageTemplates.pageFooter(request, out, visitor, false); + PageTemplates.pageEnd(out); + } finally { + out.close(); + } + } + + void printUserTags(Connection sql, PrintWriter out, com.juick.User visitor) { + ArrayList 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("

"); + for (int i = 0; i < tags.size(); i++) { + if (i > 0) { + out.print(" "); + } + String taglink = ""; + try { + taglink = "" + Utils.encodeHTML(tags.get(i).Name) + ""; + } catch (UnsupportedEncodingException e) { + } + int usagecnt = tags.get(i).UsageCnt; + if (usagecnt <= max / 5 + min) { + out.print("" + taglink + ""); + } else if (usagecnt <= max / 5 * 2 + min) { + out.print(taglink); + } else if (usagecnt <= max / 5 * 3 + min) { + out.print("" + taglink + ""); + } else if (usagecnt <= max / 5 * 4 + min) { + out.print("" + taglink + ""); + } else { + out.print("" + taglink + ""); + } + } + out.println("

"); + } + + 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 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("
"); + out.println("

Сообщение опубликовано

"); + out.println("

Поделитесь своим новым постом в социальных сетях:

"); + if (CrosspostQueries.getTwitterTokens(sql, visitor.UID) == null) { + out.println("

Отправить в Twitter

"); + } + out.println("

Отправить в LiveJournal

"); + out.println("

Отправить в ВКонтакте

"); + if (CrosspostQueries.getFacebookToken(sql, visitor.UID) == null) { + out.println("

Отправить в Facebook

"); + } + out.println("

Отправить в Google+

"); + out.println("

Ссылка на сообщение: http://juick.com/" + mid + "

"); + out.println("
"); + + 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 . + */ +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 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("
"); + + if (!msgs.isEmpty()) { + out.println("
    "); + for (int i = msgs.size() - 1; i >= 0; i--) { + com.juick.Message msg = msgs.get(i); + + String txt = PageTemplates.formatMessage(msg.Text); + + out.println("
  • "); + out.println("
    \""
    "); + out.println("
    "); + out.println(" "); + out.println(" "); + out.println("
    " + txt + "
    "); + + out.println("
    "); + out.println("
    "); + out.println("
    "); + + out.println("
    "); + out.println("
  • "); + } + out.println("
"); + } + + /* + if (msgs.size() >= 20) { + String nextpage = "?before=" + msgs.get(msgs.size() - 1); + out.println("

Читать дальше →

"); + } + */ + + out.println("
"); + + 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 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("
"); + + out.println("
"); + out.println("
"); + out.println("
To:
"); + out.println("
"); + out.println("
"); + out.println("
"); + out.println("
"); + + if (!msgs.isEmpty()) { + out.println(""); + } + + /* + if (msgs.size() >= 20) { + String nextpage = "?before=" + msgs.get(msgs.size() - 1); + out.println("

Читать дальше →

"); + } + */ + + out.println("
"); + + 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 . + */ +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(""); + out.print(""); + out.print(""); + out.print(""); + out.print(""); + out.print(""); + if (headers != null) { + out.print(headers); + } + out.print("" + title + ""); + out.print(""); + out.println(""); + out.println(""); + out.println(""); + out.flush(); + out.println(""); + } + + public static void pageNavigation(PrintWriter out, com.juick.User visitor, String search) { + out.println("
"); + out.println(" "); + out.print(" "); + out.print("
"); + out.println("
"); + if (visitor != null) { + out.print(" "); + out.print(" "); + } else { + out.println("

Чтобы добавлять сообщения и комментарии, представьтесь.

"); + } + out.println("
"); + out.println("
"); + } + + public static void pageYandexAd728(PrintWriter out, int YandexID) { + /* + out.println("
"); + out.println(""); + */ + out.println(""); + out.println(""); + out.println(""); + } + + 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(""); + } + + 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 += "" + Utils.encodeHTML(rs.getString(1)) + ""; + } 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("
"); + out.println(""); + out.println("

Кто выиграет iPod?

"); + out.println(""); + + 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(" "); + } else { + out.println(" "); + } + i++; + } + } catch (SQLException e) { + System.err.println(e); + } finally { + Utils.finishSQL(rs, stmt); + } + + out.println("
" + uname + "" + rs.getInt(3) + "
" + uname + "" + rs.getInt(3) + "
"); + out.println(""); + } + + public static void pageFooter(HttpServletRequest request, PrintWriter out, com.juick.User visitor, boolean sapeon) { + out.println("
"); + out.println(" "); + out.print("
"); + out.print("Twitter"); + out.print("ВКонтакте"); + out.print("Facebook"); + out.println("
"); + out.print("
juick.com © 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("
Спонсоры: " + links); + } + } + if ((visitor == null || visitor.UID == 1) && queryString != null && requestURI != null && requestURI.equals("/")) { + out.print("
сиам"); + } + + out.println("
"); + out.println("
"); + + if (visitor != null) { + out.println(""); + } + + out.println(""); + } + + public static void pageEnd(PrintWriter out) { + out.println(""); + } + + public static String formatTags(ArrayList tags) { + String ret = ""; + for (int i = 0; i < tags.size(); i++) { + com.juick.Tag tag = tags.get(i); + String tagName = tag.Name.replaceAll("<", "<").replaceAll(">", ">"); + try { + ret += " *"; + } catch (UnsupportedEncodingException e) { + } + } + + return ret; + } + + public static String formatTags(ArrayList tags, com.juick.User user) { + String ret = ""; + for (int i = 0; i < tags.size(); i++) { + String tag = tags.get(i); + tag = tag.replaceAll("<", "<"); + tag = tag.replaceAll(">", ">"); + try { + ret += " *" + tag + ""; + } 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 = ""; + } 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))([\\[\\{]|<)((?:ht|f)tps?://(?:www\\.)?([^\\/\\s\\\"\\)\\!]+)/?(?:[^\\]\\}](?", ">"); + + // http://juick.com/last?page=2 + // http://juick.com/last?page=2 + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))((?:ht|f)tps?://(?:www\\.)?([^\\/\\s\\n\\\"]+)/?[^\\s\\n\\\"]*)", "$1$2"); + + // (http://juick.com/last?page=2) + // (http://juick.com/last?page=2) + 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" + url + "$5"); + } + m.appendTail(sb); + msg = sb.toString(); + + return "
" + msg + "
"; + } + + public static String formatMessage(String msg) { + msg = msg.replaceAll("&", "&"); + msg = msg.replaceAll("<", "<"); + msg = msg.replaceAll(">", ">"); + + // -- + // — + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))\\-\\-?((?=\\s)|(?=\\Z))", "$1—$2"); + + // http://juick.com/last?page=2 + // juick.com + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))((?:ht|f)tps?://(?:www\\.)?([^\\/\\s\\n\\\"]+)/?[^\\s\\n\\\"]*)", "$1$3"); + + // [link text][http://juick.com/last?page=2] + // link text + msg = msg.replaceAll("\\[([^\\]]+)\\]\\[((?:ht|f)tps?://[^\\]]+)\\]", "$1"); + msg = msg.replaceAll("\\[([^\\]]+)\\]\\(((?:ht|f)tps?://[^\\)]+)\\)", "$1"); + + // #12345 + // #12345 + msg = msg.replaceAll("((?<=\\s)|(?<=\\A)|(?<=\\p{Punct}))#(\\d+)((?=\\s)|(?=\\Z)|(?=\\))|(?=\\.)|(?=\\,))", "$1#$2$3"); + + // #12345/65 + // #12345/65 + msg = msg.replaceAll("((?<=\\s)|(?<=\\A)|(?<=\\p{Punct}))#(\\d+)/(\\d+)((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1#$2/$3$4"); + + // *bold* + // bold + msg = msg.replaceAll("((?<=\\s)|(?<=\\A)|(?<=\\p{Punct}))\\*([^\\*\\n<>]+)\\*((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1$2$3"); + + // /italic/ + // italic + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))/([^\\/\\n<>]+)/((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1$2$3"); + + // _underline_ + // underline + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))_([^\\_\\n<>]+)_((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1$2$3"); + + // /12 + // /12 + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))\\/(\\d+)((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1/$2$3"); + + // @username@jabber.org + // @username@jabber.org + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))@([\\w\\-\\.]+@[\\w\\-\\.]+)((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1@$2$3"); + + // @username + // @username + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))@([\\w\\-]{2,16})((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1@$2$3"); + + // (http://juick.com/last?page=2) + // (juick.com) + 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$4$5"); + } + m.appendTail(sb); + msg = sb.toString(); + + // > citate + msg = msg.replaceAll("(?:(?<=\\n)|(?<=\\A))> *(.*)?(\\n|(?=\\Z))", "
$1
"); + msg = msg.replaceAll("
", "\n"); + + msg = msg.replaceAll("\n", "
\n"); + return msg; + } + + public static void printMessages(PrintWriter out, Connection sql, com.juick.User user, ArrayList mids, com.juick.User visitor, int YandexID, int ad_mid) { + ArrayList 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 blUIDs = new ArrayList(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 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("
"); + out.println(" "); + out.println("
@" + msg.User.UName + ":" + tagsStr + "
"); + out.println("
"); + if (msg.AttachmentType != null) { + String fname = msg.MID + "." + msg.AttachmentType; + out.println("

\"\"/

"); + } + out.println("

" + txt + "

"); + if (msg.AttachmentType != null) { + out.println("
"); + } + out.print(" "); + + out.print(" "); + out.print("
"); + } + } +} 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 . + */ +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 mids = MessagesQueries.getUserBlog(sql, uid, 0, 0); + if (mids.isEmpty()) { + response.sendError(404); + return; + } + + ArrayList msgs = MessagesQueries.getMessages(sql, mids); + + response.setContentType("application/rss+xml; charset=UTF-8"); + PrintWriter out = response.getWriter(); + try { + out.println(""); + out.println(""); + out.println(""); + out.println(""); + out.println("" + uname + " - Juick"); + out.println("http://juick.com/" + uname + "/"); + out.println("The latest messages by @" + uname + " at Juick"); + out.println("http://i.juick.com/a/" + uid + ".png" + uname + " - Juickhttp://juick.com/" + uname + "/"); + + Iterator i = msgs.iterator(); + while (i.hasNext()) { + Message msg = i.next(); + + out.println(""); + out.println("http://juick.com/" + msg.User.UName + "/" + msg.MID + ""); + out.println("http://juick.com/" + msg.User.UName + "/" + msg.MID + ""); + + out.print("<![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("]]>"); + out.println(""); + + synchronized (sdfSQL) { + try { + Date date = sdfSQL.parse(msg.TimestampString); + out.println("" + sdfRSS.format(date) + ""); + } catch (Exception e) { + System.err.println("PARSE EXCEPTION: " + msg.TimestampString); + } + } + + out.println("http://juick.com/" + msg.User.UName + "/" + msg.MID + ""); + if (!msg.Tags.isEmpty()) { + for (int n = 0; n < msg.Tags.size(); n++) { + out.println("" + msg.Tags.get(n) + ""); + } + } + if (msg.AttachmentType != null) { + if (msg.AttachmentType.equals("jpg")) { + out.println(""); + out.println(""); + } else if (msg.AttachmentType.equals("png")) { + out.println(""); + out.println(""); + } + } + out.println(""); + out.println(""); + } + + out.println(""); + } 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 . + */ +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("
"); + out.println("
"); + out.println("
"); + out.println("
"); + out.println("

Имя пользователя:

"); + out.println("

Пароль:

"); + out.println("

"); + out.println("
"); + out.println("
"); + out.println("
"); + out.println("
"); // 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 . + */ +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("
"); + + out.print("

"); + if (type.charAt(0) == 'f') { + out.print("\"Facebook\"/"); + } else if (type.charAt(0) == 'v') { + out.print("\"VKontakte\"/"); + } else if (type.charAt(0) == 'x') { + out.print("\"XMPP\"/"); + } + out.println(account + "

"); + + out.println("

Связать с существующим аккаунтом Juick

"); + out.println("
"); + out.println(""); + out.println(""); + out.println(""); + if (visitor != null) { + out.println(""); + } else { + out.println("

Имя пользователя:

"); + out.println("

Пароль:

"); + out.println("

"); + } + out.println("
"); + + out.println("
"); + + out.println("

Создать новый аккаунт Juick

"); + out.println("
"); + out.println(""); + out.println(""); + out.println(""); + out.println("

Имя пользователя:
(От 2-х до 16-и латинских символов и/или цифр, дефис)

"); + out.println("

Пароль:
(от 6-и до 32-х символов)

"); + out.println("

"); + out.println("
"); + + out.println("
"); + + 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 = "" + rs.getString(1) + ""; + } + } 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 = "" + rs.getString(1) + ""; + } + } 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 . + */ +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 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 = ""; + if (paramTag != null && TagQueries.getTagNoIndex(sql, paramTag.TID)) { + head += ""; + } else if (paramBefore > 0 || paramShow != null) { + head += ""; + } + PageTemplates.pageHead(out, title, head); + PageTemplates.pageNavigation(out, visitor, null); + pageUserColumn(out, sql, user, visitor); + + if (mids.size() > 0) { + out.println("
"); + + if (paramTag != null) { + out.println("

← Все записи с тегом " + Utils.encodeHTML(paramTag.Name) + "

"); + } + + 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 += "&show=" + paramShow; + } + if (paramTag != null) { + nextpage += "&tag=" + URLEncoder.encode(paramTag.Name, "UTF-8"); + } + if (paramSearch != null) { + nextpage += "&search=" + URLEncoder.encode(paramSearch, "UTF-8"); + } + out.println("

Читать дальше →

"); + } + + out.println("
"); + } + + 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 = ""; + PageTemplates.pageHead(out, "Теги " + user.UName, head); + PageTemplates.pageNavigation(out, visitor, null); + pageUserColumn(out, sql, user, visitor); + + out.println("
"); + out.println("

" + pageUserTags(sql, user, visitor, 0) + "

"); + out.println("
"); + + 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 = ""; + PageTemplates.pageHead(out, "Подписки " + user.UName, head); + PageTemplates.pageNavigation(out, visitor, null); + pageUserColumn(out, sql, user, visitor); + + out.println("
"); + out.println(""); + + 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(""); + } + out.print(""); + cnt++; + } + } catch (SQLException e) { + System.err.println(e); + } finally { + Utils.finishSQL(rs, stmt); + } + + out.println("
" + rs.getString(2) + "
"); + out.println("
"); + + 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 = ""; + PageTemplates.pageHead(out, "Читатели " + user.UName, head); + PageTemplates.pageNavigation(out, visitor, null); + pageUserColumn(out, sql, user, visitor); + + out.println("
"); + out.println(""); + + 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(""); + } + out.print(""); + cnt++; + } + } catch (SQLException e) { + System.err.println(e); + } finally { + Utils.finishSQL(rs, stmt); + } + + out.println("
" + rs.getString(2) + "
"); + out.println("
"); + + 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(""); + } + + 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 = "" + tag + ""; + } catch (UnsupportedEncodingException e) { + } + + if (tags[i].UsageCnt > maxUsageCnt / 3 * 2) { + ret += "" + tag + " "; + } else if (tags[i].UsageCnt > maxUsageCnt / 3) { + ret += "" + tag + " "; + } 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 . + */ +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 = ""; + if (paramView != null) { + headers += ""; + } + if (msg.Hidden) { + headers += ""; + } + PageTemplates.pageHead(out, title, headers); + PageTemplates.pageNavigation(out, visitor, null); + + out.println("
"); + printMessage(out, sql, msg, visitor); + printReplies(out, sql, msg, visitor, listview); + out.println("
"); + + PageTemplates.pageFooter(request, out, visitor, false); + + out.println(""); + + 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 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 = "" + tagsStr + ""; + } + + out.println("
    "); + out.println("
  • "); + out.println("
    \""
    "); + out.println("
    "); + out.println("
    "); + out.println("
    @" + msg.User.UName + ":" + tagsStr + "
    "); + out.println("
    " + PageTemplates.formatJSLocalTime(msg.TimestampString) + "
    "); + out.println("
    " + txt + "
    "); + + if (msg.AttachmentType != null) { + out.println("
    \"\"/
    "); + } + + 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("
    "); + out.println("
    "); + out.println("
    "); + } + + ArrayList recomm = MessagesQueries.getMessageRecommendations(sql, msg.MID); + if (!recomm.isEmpty()) { + out.print("
    Рекомендовали (" + recomm.size() + "): "); + for (int i = 0; i < recomm.size(); i++) { + if (i > 0) { + out.print(", "); + } + out.print("@" + recomm.get(i) + ""); + } + out.println("
    "); + } + out.println("
    "); + out.println("
  • "); + + out.println("
  • "); + out.println("
"); + + return msg; + } + + public static void printReplies(PrintWriter out, Connection sql, com.juick.Message msg, com.juick.User visitor, boolean listview) { + ArrayList replies = MessagesQueries.getReplies(sql, msg.MID); + + ArrayList blUIDs = new ArrayList(); + 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("
"); + out.print("
"); + if (listview) { + out.print("Показать деревом"); + } else { + if (foldable) { + out.print("li').show(); $('#replies .msg-comments').hide(); $('#unfoldall').hide(); return false\">Раскрыть все · "); + } + out.print("Показать списком"); + } + out.print("
"); + out.println("

Ответы (" + replies.size() + ")

"); + out.println("
"); + + out.println("
    "); + if (listview) { + printList(out, replies, visitor); + } else { + printTree(out, replies, visitor, 0, 0, false); + } + out.println("
"); + + 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 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("
  • 0) { + out.print("margin-left: " + margin + "px;"); + } + if (hidden) { + out.print("display:none;"); + } + out.println("\">"); + if (msg.User.Banned == false) { + out.println("
    \""
    "); + } else { + out.println("
    "); + } + out.println("
    "); + out.println("
    "); + if (msg.User.Banned == false) { + out.println(" "); + } else { + out.println("
    [удалено]:
    "); + } + out.println(" "); + out.println("
    " + PageTemplates.formatMessage(msg.Text) + "
    "); + if (msg.AttachmentType != null) { + out.println("
    \"\"/
    "); + } + if (msg.VisitorCanComment) { + out.println(" "); + out.println("
    "); + } else if (visitor == null) { + out.println(" "); + } + + int childs = msg.getChildsCount(); + if (ReplyTo == 0 && childs > 1 && replies.size() > 10) { + out.println(" "); + + } + out.println("
    "); + out.println("
  • "); + + 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 replies, com.juick.User visitor) { + for (int i = 0; i < replies.size(); i++) { + com.juick.Message msg = replies.get(i); + + out.print("
  • "); + if (msg.User.Banned == false) { + out.println("
    \""
    "); + } else { + out.println("
    "); + } + out.println("
    "); + out.println("
    "); + if (msg.User.Banned == false) { + out.println(" "); + } else { + out.println("
    [удалено]:
    "); + } + out.println(" "); + out.println("
    " + PageTemplates.formatMessage(msg.Text) + "
    "); + if (msg.AttachmentType != null) { + out.println("
    \"\"/
    "); + } + out.print("
    /" + msg.RID); + if (msg.ReplyTo > 0) { + out.print(" в ответ на /" + msg.ReplyTo + ""); + } + if (msg.VisitorCanComment) { + out.println(" · Ответить
    "); + out.println("
    "); + } else if (visitor == null) { + out.println(" "); + } + out.println("
    "); + out.println("
  • "); + } + } +} 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 . + */ +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 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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("'", "'").replaceAll("\"", """).replaceAll("\n", " "); + } + + 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 . + */ +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 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 cached; + long cacheUpdated; + + @SuppressWarnings("unchecked") + public Map 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) 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(); + } + + 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 data = sapeConnection.getData(); + + if (data.containsKey("__sape_delimiter__")) { + linkDelimiter = (String) data.get("__sape_delimiter__"); + } + + if (data.containsKey(requestUri)) { + pageLinks = new ArrayList(((Map) 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(((Map) newUrl).values()); + } else { + pageLinks = new ArrayList(Arrays.asList((String) newUrl)); + } + } + } + + this.showCode = showCode; + } + private String linkDelimiter = "."; + private List pageLinks = new ArrayList(); + + public String render() { + return render(-1); + } + + public String render(int count) { + StringBuilder s = new StringBuilder(); + + if (count < 0) { + count = pageLinks.size(); + } + + for (Iterator 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, ""); + s.append(""); + } + + 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 + +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 Map instances and objects + * that are represented by {@link SerializedPhpParser.PhpObject} instances. + *

    + * Example of use: + *

    + *      String input = "O:8:"TypeName":1:{s:3:"foo";s:3:"bar";}";
    + *      SerializedPhpParser serializedPhpParser = new SerializedPhpParser(input);
    + *      Object result = serializedPhpParser.parse();
    + * 
    + * + * The result object will be a PhpObject 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 parseArray() { + int arrayLen = readLength(); + Map result = new LinkedHashMap(); + 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 attributes = new HashMap(); + + @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 @@ + + + + Main + com.juick.http.www.Main + + + Main + / + + + + 30 + + + diff --git a/src/main/webapp/favicon.png b/src/main/webapp/favicon.png new file mode 100644 index 00000000..bc7161e2 Binary files /dev/null and b/src/main/webapp/favicon.png differ diff --git a/src/main/webapp/logo.png b/src/main/webapp/logo.png new file mode 100644 index 00000000..933f6099 Binary files /dev/null and b/src/main/webapp/logo.png 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=$('
    '); + 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.html('
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+msg.body+'
    '+ + ''+ + ''+ + '
    '); + + 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('

    Пожалуйста, введите теги сообщения

    '); + return false; + } else if($('#newmessage textarea').val().length==0) { + openDialog('

    Пожалуйста, введите текст сообщения

    '); + return false; + } + return true; +} + +function showMoreReplies(id) { + $('#'+id+' .msg-comments').hide(); + + var replies=$('#replies>li'); + var flagshow=0; + for(var i=0; i'); + c.before(''); + c.append('
    '); + } + // $('#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('
    '); + 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=$(''); + 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=$(''); + 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="
    Ссылка на сообщение:"; + html+="
    "+hlink+"
    "; + html+="Номер сообщения:"; + html+="
    "+mlink+"
    "; + html+="Поделиться:
      "; + html+="
    • "; + html+="
    • "; + html+="
    • "; + html+="
    • "; + html+="
    "; + + 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(""); + $('#dialogw img').css('max-height',height+'px'); + return false; + } else { + openDialog(""); + $('#dialogw img').css('max-height',height+'px'); + return false; + } +} + +function openDialog(html) { + var dhtml="
    "; + dhtml+=html; + dhtml+="
    "; + $('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='

    Пожалуйста, представьтесь:' + +'Facebook ' + +'ВКонтакте

    ' + +'

    Уже зарегистрированы?

    ' + +'
    ' + +'
    ' + +'
    ' + +'' + +'
    '; + openDialog(html); + return false; +} + +/******************************************************************************/ + +function likeMessage(e,mid) { + $.ajax({ + url: 'http://juick.com/like?mid='+mid, + type: 'POST' + }).done(function() { + $(e).closest("article").append("

    OK!

    "); + }).fail(function() { + $(e).closest("article").append("

    Ошибка

    "); + }); + 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("

    OK!

    "); + }); + 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("

    OK!

    "); + }); + 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() : $(''); + + 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 = $('').appendTo('body')) + ).append(this.clone); + } + + }; + +})(jQuery); + +/******************************************************************************/ + +$(document).ready(function() { + $('textarea').autoResize({ + extraSpace: 0, + minHeight: 1 + }); + + $('textarea.reply').click(function () { + $(this).addClass("narrow"); + $(this).after('
    '); + $(this).parent().after(''); + $(this).off('click'); + }); + + $('textarea.replypm').click(function () { + $(this).addClass("narrowpm"); + $(this).parent().after(''); + $(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; } +} -- cgit v1.2.3