diff --git a/2355/3/config.ru b/2355/3/config.ru new file mode 100644 index 000000000..2601f422d --- /dev/null +++ b/2355/3/config.ru @@ -0,0 +1,5 @@ +require 'sinatra/base' + +Dir.glob('./{controllers,models}/*.rb').each { |file| require file } + +map('/') { run IndexController } diff --git a/2355/3/controllers/index_controller.rb b/2355/3/controllers/index_controller.rb new file mode 100644 index 000000000..3e6d4473f --- /dev/null +++ b/2355/3/controllers/index_controller.rb @@ -0,0 +1,53 @@ +require 'sinatra' +require 'slim' +require 'ohm' + +# Main controller +class IndexController < Sinatra::Base + set :views, File.expand_path(File.join(__FILE__, '../../views')) + + get '/' do + slim :header_footer do + slim :index + end + end + + get '/add' do + slim :header_footer do + slim :new + end + end + + post '/add' do + Ohm.redis.call 'SET', 'article' + ((Ohm.redis.call 'GET', 'articles_count').to_i + 1).to_s, params[:link].to_s + Ohm.redis.call 'SET', 'articles_count', ((Ohm.redis.call 'GET', 'articles_count').to_i + 1).to_s + require_relative './models/article_rate.rb' + rating = ArticleRate.new(params[:link].to_s) + coment_count = rating.article.comments.size + Ohm.redis.call 'SET', 'article' + (Ohm.redis.call 'GET', 'articles_count') + '_comments_count', coment_count.to_s + rating.article_rate + rating.article.comments.size.times do |index| + text = rating.article.comments[index].text + Ohm.redis.call 'SET', 'article' + (Ohm.redis.call 'GET', 'articles_count') + '_comment' + (index + 1).to_s, text + end + Ohm.redis.call 'SET', 'article' + (Ohm.redis.call 'GET', 'articles_count') + '_rate', rating.rate.to_i.to_s + redirect '/' + end + + # I use 'id' in show.slim, so it needed here + # rubocop:disable Lint/UselessAssignment + get '/show/:id' do + slim :header_footer do + slim :show do + id = params['id'] + end + end + end + # rubocop:enable Lint/UselessAssignment + + get '/annihilate' do + slim :header_footer do + slim :annihilate + end + end +end diff --git a/2355/3/models/article.rb b/2355/3/models/article.rb new file mode 100644 index 000000000..cd645bc13 --- /dev/null +++ b/2355/3/models/article.rb @@ -0,0 +1,13 @@ +require_relative './comment_parser.rb' + +# This class describes article +class Article + attr_reader :url, :comments + + def initialize(url) + @url = url + parser = CommentParser.new + parser.parse(url) + @comments = parser.comments + end +end diff --git a/2355/3/models/article_rate.rb b/2355/3/models/article_rate.rb new file mode 100644 index 000000000..c04792315 --- /dev/null +++ b/2355/3/models/article_rate.rb @@ -0,0 +1,29 @@ +require_relative './text_analytics.rb' +require_relative './article.rb' + +# This class is needed to get article rate +class ArticleRate + attr_reader :rate, :article + + def initialize(url) + @rate = 0 + @article = Article.new(url) + end + + def all_comments_rate + docs = [] + @article.comments.each do |comment| + docs << { 'id' => 1, 'language' => 'ru', 'text' => comment.text.to_s } + end + documents = { 'documents' => docs } + TextAnalytics.new(documents).analyze['documents'] + end + + def article_rate + comments_rate = all_comments_rate + comments_rate.each do |comment| + @rate += ((comment['score'] * 200) - 100) + end + @rate /= comments_rate.size + end +end diff --git a/2355/3/models/comment.rb b/2355/3/models/comment.rb new file mode 100644 index 000000000..42f7a8d96 --- /dev/null +++ b/2355/3/models/comment.rb @@ -0,0 +1,13 @@ +require_relative './text_analytics.rb' + +# This class describes comment +class Comment + attr_reader :text, :rate + + def initialize(text) + @text = text + docs = { 'id' => 1, 'language' => 'ru', 'text' => text } + documents = { 'documents' => [docs] } + @rate = (TextAnalytics.new(documents).analyze['documents'][0]['score'] * 200 - 100).to_i + end +end diff --git a/2355/3/models/comment_parser.rb b/2355/3/models/comment_parser.rb new file mode 100644 index 000000000..4a9766b2f --- /dev/null +++ b/2355/3/models/comment_parser.rb @@ -0,0 +1,20 @@ +require 'selenium-webdriver' + +# This class parse web page for comments +class CommentParser + attr_reader :comments + + def initialize + @comments = [] + @driver = Selenium::WebDriver.for :chrome + end + + def parse(url) + @driver.get url + begin + @driver.find_element(:class, 'news-form__button').click + ensure + @comments = @driver.find_elements(:class, 'news-comment__speech') + end + end +end diff --git a/2355/3/models/text_analytics.rb b/2355/3/models/text_analytics.rb new file mode 100644 index 000000000..14241fa9c --- /dev/null +++ b/2355/3/models/text_analytics.rb @@ -0,0 +1,40 @@ +require 'net/https' +require 'uri' +require 'json' + +# This class responsible for analyzing comments +class TextAnalytics + attr_reader :documents, :access_key, :uri + + def initialize(documents) + @documents = documents + @access_key = '7614507e97184bfd9f38e1d93fa130e8' # use it, if you want... I don't mind + path = '/text/analytics/v2.0/sentiment' + @uri = URI('https://westcentralus.api.cognitive.microsoft.com' + path) + end + + # This method is written in accordance with the documentation of Azura, + # so it's not my fault that reek swears on TooManyStatements and Rubocop swears on EVERYTHING! + # This method smells of :reek:TooManyStatements + # rubocop:disable Style/HashSyntax + # rubocop:disable Style/NestedParenthesizedCalls + # rubocop:disable Style/ColonMethodCall + # rubocop:disable Style/RedundantParentheses + # rubocop:disable Lint/ParenthesesAsGroupedExpression + def analyze + request = Net::HTTP::Post.new(uri) + request['Content-Type'] = 'application/json' + request['Ocp-Apim-Subscription-Key'] = @access_key + request.body = @documents.to_json + + response = Net::HTTP.start(@uri.host, @uri.port, :use_ssl => @uri.scheme == 'https') do |http| + http.request(request) + end + JSON.parse(JSON::pretty_generate (JSON (response.body))) + end + # rubocop:enable Style/HashSyntax + # rubocop:enable Style/NestedParenthesizedCalls + # rubocop:enable Style/ColonMethodCall + # rubocop:enable Style/RedundantParentheses + # rubocop:enable Lint/ParenthesesAsGroupedExpression +end diff --git a/2355/3/views/annihilate.slim b/2355/3/views/annihilate.slim new file mode 100644 index 000000000..42aa50028 --- /dev/null +++ b/2355/3/views/annihilate.slim @@ -0,0 +1,15 @@ +main[role="main"] + section.jumbotron.text-center + .container + - Ohm.redis.call "FLUSHALL" + - Ohm.redis.call "SET" "articles_count" "0" + .alert.alert-success[role="alert"] + h3.jumbotron-heading + | All articles have been deleted! + p.lead.text-muted + | Choose what you want to do + p + a.btn.btn-primary.my-2[href="/add"] + | Add new article + a.btn.btn-primary.my-2[href="/"] + | Return to main page \ No newline at end of file diff --git a/2355/3/views/header_footer.slim b/2355/3/views/header_footer.slim new file mode 100644 index 000000000..5a81de153 --- /dev/null +++ b/2355/3/views/header_footer.slim @@ -0,0 +1,38 @@ +| +html[lang="en"] + head + meta[charset="utf-8"] + meta[name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"] + title + | Onliner analyzer + link[rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"] + body + header + #navbarHeader.collapse.bg-dark + .container + .row + .col-sm-8.col-md-7.py-4 + h4.text-white + | About + p.text-muted + | This application will help you to analyze the articles from the portal "Onliner.by" and find out their rating based on the analysis of comments. You can also find out the rating of each individual comment by viewing them in the appropriate section. It supports adding a new article for analysis, viewing the list of all added articles and rating comments. + .navbar.navbar-dark.bg-dark.shadow-sm + .container.d-flex.justify-content-between + a.navbar-brand.d-flex.align-items-center[href="/"] + svg.mr-2[xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"] + path[d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"] + circle[cx="12" cy="13" r="4"] + strong + | Onliner Analyzer + button.navbar-toggler[type="button" data-toggle="collapse" data-target="#navbarHeader" aria-controls="navbarHeader" aria-expanded="false" aria-label="Toggle navigation"] + span.navbar-toggler-icon + == yield + footer.text-muted + .container + p.float-right + | ©WhiteNiceFlower + p.float-left + | 27.07.2018 + script[src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"] + script[src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"] + script[src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"] \ No newline at end of file diff --git a/2355/3/views/index.slim b/2355/3/views/index.slim new file mode 100644 index 000000000..468f05673 --- /dev/null +++ b/2355/3/views/index.slim @@ -0,0 +1,46 @@ +main[role="main"] + section.jumbotron.text-center + .container + h1.jumbotron-heading + | There are analyzed articles: + table.table.table-striped.table-bordered + thead + tr + th + | # + th + | Article(Link) + th + | Raiting + th + | About + tbody + - if "#{Ohm.redis.call 'GET', 'articles_count'}".to_i == 0 + tr + th[scope="row"] + | There + td + | are + td + | NO + td + | articles + - "#{Ohm.redis.call 'GET', 'articles_count'}".to_i.times do |j| + tr + th[scope="row"] + | #{j + 1} + td + a[href="#{Ohm.redis.call 'GET', 'article' + "#{j + 1}"}"] + | #{Ohm.redis.call 'GET', 'article' + "#{j + 1}"} + td + | #{Ohm.redis.call 'GET', 'article' + "#{j + 1}" + '_rate'} + td + a[href="/show/#{j + 1}"] + | > + p.lead.text-muted + | Choose what you want to do + p + a.btn.btn-primary.my-2[href="/add"] + | Add new article + a.btn.btn-primary.my-2[href="/annihilate"] + | Delete all articles \ No newline at end of file diff --git a/2355/3/views/new.slim b/2355/3/views/new.slim new file mode 100644 index 000000000..27e5a9f48 --- /dev/null +++ b/2355/3/views/new.slim @@ -0,0 +1,13 @@ +main[role="main"] + section.jumbotron.text-left + .container + h3.jumbotron-heading + | Add new article + form.form-inline[method="post"] + .form-group.row + label.col-sm-2.col-form-label[for="link"] + | Link + .col-sm-10 + input#link.form-control[type="text" name="link" placeholder="https://onliner.by/..."] + button.btn.btn-primary[type="submit" href="/"] + | Add diff --git a/2355/3/views/show.slim b/2355/3/views/show.slim new file mode 100644 index 000000000..db4e1575e --- /dev/null +++ b/2355/3/views/show.slim @@ -0,0 +1,29 @@ +main[role="main"] + section.jumbotron.text-center + .container + h1.jumbotron-heading + - id = yield + | #{Ohm.redis.call "GET", "article" + "#{id}"} + table.table.table-striped.table-bordered + thead + tr + th + | # + th + | Comment + th + | Rate + tbody + - "#{Ohm.redis.call "GET", "article" + "#{id}" + "_comments_count"}".to_i.times do |i| + - require_relative '../models/comment.rb' + - comment = Comment.new("#{Ohm.redis.call "GET", "article" + "#{id}" + "_comment" + "#{i + 1}"}") + tr + th + | #{i + 1} + th + - if i == 0 + | #{'Лучший комментарий: '} #{comment.text} + - else + | #{comment.text} + th + | #{comment.rate}