Compare commits

...

2 commits

Author SHA1 Message Date
cb2ac7c469 page parser 2025-03-21 08:24:02 +03:00
1fc263c931 pre-release 2025-03-21 08:23:35 +03:00
12 changed files with 234 additions and 70 deletions

View file

@ -7,5 +7,5 @@ edition = "2021"
actix-web = "4" actix-web = "4"
rust-discord-activity = { git = "https://github.com/AmokDev/rust-discord-activity", version = "0.3.1" } rust-discord-activity = { git = "https://github.com/AmokDev/rust-discord-activity", version = "0.3.1" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
tokio = "1.44.1" serde_json = "1.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

1
app/data.json Normal file
View file

@ -0,0 +1 @@
{"episode":6,"season":1,"timestamps":[18000,2597000],"name":"Элементарно","image_url":"https://statichdrezka.ac/i/2020/12/17/xe6ea25038d76rc83x59z.jpeg"}

View file

@ -10,31 +10,53 @@ use rust_discord_activity::{
}; };
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use tokio::time::Duration; use tokio::time::Duration;
use crate::utils::json_database::load_from_json;
pub async fn start_presence() { pub async fn start_presence() {
let mut client = DiscordClient::new("1351391108088987748"); let mut client = DiscordClient::new("1351391108088987748");
let _ = client.connect(); let _ = client.connect();
let limg = Some(String::from("https://i.ibb.co/SDDCLdHN/discord-logo-discord-icon-transparent-free-png.png"));
let asset = Asset::new(limg.clone(), Some(String::from("#noWar")), limg, None);
let now_in_millis = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis();
let timestamps = Timestamp::new(Some(now_in_millis - 10000), Some(now_in_millis + 100000));
let mut activity = Activity::new();
activity
.set_state(Some("Сезон 1 Серия 5".into()))
.set_activity_type(Some(ActivityType::WATCHING))
.set_details(Some("Элементарно".parse().unwrap()))
.set_timestamps(Some(timestamps))
.set_assets(Some(asset))
.set_instance(Some(true));
let payload = Payload::new(EventName::Activity, EventData::Activity(activity));
let _ = client.send_payload(payload);
println!(":: Rich Presence started!"); println!(":: Rich Presence started!");
loop { loop {
tokio::time::sleep(Duration::from_secs(5)).await; let post_data = load_from_json();
let mut episode: u8 = 0;
let mut season: u8 = 0;
let mut timestamps: [u128; 2] = [0, 0];
let mut name: String = String::from("name");
let mut image_url: String = String::from("hdrezcord-logo");
if let Ok(data) = post_data {
episode = data.episode;
season = data.season;
timestamps = data.timestamps;
name = data.name;
image_url = data.image_url;
}
let asset = Asset::new(Some(image_url), Some(String::from("#noWar")), None, None);
let now_in_millis = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis();
let timestamp = Timestamp::new(Some(now_in_millis - timestamps[0]), Some(now_in_millis + (timestamps[1] - timestamps[0])));
let mut activity = Activity::new();
let mut subtitle = format!("Сезон {season} Серия {episode}");
if season == 0 && episode == 0 {
subtitle = format!("Фильм");
}
activity
.set_state(Some(subtitle).into())
.set_activity_type(Some(ActivityType::WATCHING))
.set_details(Some(name))
.set_timestamps(Some(timestamp))
.set_assets(Some(asset))
.set_instance(Some(true));
let payload = Payload::new(EventName::Activity, EventData::Activity(activity));
if episode == 0 && timestamps[0] == 0 && timestamps[1] == 0 {
continue;
}
let _ = client.send_payload(payload);
tokio::time::sleep(Duration::from_secs(1)).await;
} }
} }

View file

@ -1,12 +1,12 @@
mod network; mod network;
mod discord; mod discord;
mod utils;
use network::api::start_api; use network::api::start_api;
use discord::rpc::start_presence; use discord::rpc::start_presence;
#[actix_web::main] #[actix_web::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
tokio::spawn(start_presence()); let _ = tokio::join!(start_api(), start_presence());
start_api().await?;
Ok(()) Ok(())
} }

View file

@ -1,23 +1,20 @@
use actix_web::{get, App, HttpResponse, HttpServer, Responder}; use actix_web::{post, App, HttpResponse, HttpServer, web};
use serde::Serialize; use crate::utils::json_database::{save_to_json, PostData};
#[derive(Serialize)] #[post("/set_presence")]
struct MyResponse { async fn set_presence(data: web::Json<PostData>) -> HttpResponse {
message: String, println!("{:?}", data);
let _ = save_to_json(&data);
HttpResponse::Ok().finish()
} }
#[get("/")] pub async fn start_api() -> std::io::Result<()> {
async fn hello() -> impl Responder {
HttpResponse::Ok().json(MyResponse { message: "Hello, world!".to_string() })
}
pub async fn start_api() -> std::io::Result<()> {
println!(":: API Started!"); println!(":: API Started!");
HttpServer::new(|| { HttpServer::new(|| {
App::new() App::new()
.service(hello) .service(set_presence)
}) })
.bind("127.0.0.1:2006")? .bind("127.0.0.1:2006")?
.run() .run()
.await .await
} }

View file

@ -0,0 +1,27 @@
use std::fs::File;
use std::io::{Read, Write};
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct PostData {
pub episode: u8,
pub season: u8,
pub timestamps: [u128; 2],
pub name: String,
pub image_url: String,
}
pub fn save_to_json(post_data: &PostData) -> Result<(), Box<dyn std::error::Error>> {
let json_string = serde_json::to_string(post_data)?;
let mut file = File::create("data.json")?;
file.write_all(json_string.as_bytes())?;
Ok(())
}
pub fn load_from_json() -> Result<PostData, Box<dyn std::error::Error>> {
let mut file = File::open("data.json")?;
let mut json_string = String::new();
file.read_to_string(&mut json_string)?;
let post_data = serde_json::from_str(&json_string)?;
Ok(post_data)
}

1
app/src/utils/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod json_database;

95
content.js Normal file
View file

@ -0,0 +1,95 @@
function get_episode() {
const active = document.querySelector('li.b-simple_episode__item.active');
if (active) {
const textContent = active.textContent.trim();
const episodeNumberString = textContent.replace("Серия ", "");
const episodeNumber = parseInt(episodeNumberString, 10);
if (!isNaN(episodeNumber)) {
return episodeNumber;
} else {
return 0;
}
} else {
return 0;
}
}
function get_season() {
const active = document.querySelector('li.b-simple_season__item.active');
if (active) {
const textContent = active.textContent.trim();
const episodeNumberString = textContent.replace("Сезон ", "");
const episodeNumber = parseInt(episodeNumberString, 10);
if (!isNaN(episodeNumber)) {
return episodeNumber;
} else {
return 0;
}
} else {
return 0;
}
}
function timeToMilliseconds(timeString) {
const [hours, minutes, seconds] = timeString.split(':').map(Number);
return (hours * 3600 + minutes * 60 + seconds) * 1000;
}
function get_timestamps() {
const timeElements = document.querySelectorAll('pjsdiv[style*="pointer-events: none;"] noindex');
const times = [];
timeElements.forEach(element => {
times.push(element.textContent);
});
const currentTime = times[0];
const totalTime = times[1].replace("/ ", "");
if (currentTime == NaN || totalTime == NaN) {
console.warn("timestamps not found");
return [0, 0];
}
return [timeToMilliseconds(currentTime), timeToMilliseconds(totalTime)];
}
function get_name() {
let element = document.querySelector('.b-post__title h1[itemprop="name"]');
return element.textContent;
}
function get_image_url() {
const element = document.querySelector('.b-post__infotable_left .b-sidecover a img');
return element.src;
}
if (window.location.href.includes("flymaterez.net")) {
setInterval(async () => {
try {
chrome.storage.local.get(['pluginEnabled'], function(result) {
if (result.pluginEnabled === true) {
chrome.runtime.sendMessage({
type: "send_rpc_request",
title: document.title,
url: window.location.href,
episode: get_episode(),
season: get_season(),
timestamps: get_timestamps(),
name: get_name(),
image_url: get_image_url(),
});
}
console.log(result.pluginEnabled);
});
} catch (error) {
console.warn(error);
}
}, 1000);
}

View file

@ -13,15 +13,22 @@
"default_popup":"popup.html" "default_popup":"popup.html"
}, },
"permissions": [ "permissions": [
"storage" "storage",
"activeTab",
"scripting",
"tabs"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_end"
}
], ],
"background": { "background": {
"service_worker": "service-worker.js" "service_worker": "service-worker.js"
}, },
"web_accessible_resources": [ "host_permissions": [
{ "<all_urls>"
"resources": ["rpc.js"],
"matches": ["<all_urls>"]
}
] ]
} }

View file

@ -1,3 +1,5 @@
const on = document.getElementById("on"); const on = document.getElementById("on");
const off = document.getElementById("off"); const off = document.getElementById("off");
const plugin_status = document.getElementById("status"); const plugin_status = document.getElementById("status");
@ -24,9 +26,9 @@ function toggle_status() {
function get_status() { function get_status() {
chrome.storage.local.get(['pluginEnabled'], function(result) { chrome.storage.local.get(['pluginEnabled'], function(result) {
if (result.pluginEnabled === true) { if (result.pluginEnabled === true) {
plugin_status.innerHTML = "Plugin is disabled";
} else if (result.pluginEnabled === false) {
plugin_status.innerHTML = "Plugin is enabled"; plugin_status.innerHTML = "Plugin is enabled";
} else if (result.pluginEnabled === false) {
plugin_status.innerHTML = "Plugin is disabled";
} }
console.log(result.pluginEnabled); console.log(result.pluginEnabled);
}); });
@ -37,9 +39,9 @@ document.addEventListener("DOMContentLoaded", function() {
if (result.pluginEnabled === undefined) { if (result.pluginEnabled === undefined) {
chrome.storage.local.set({ pluginEnabled: false }); chrome.storage.local.set({ pluginEnabled: false });
} else if (result.pluginEnabled === true) { } else if (result.pluginEnabled === true) {
on.classList.toggle("is-outlined");
} else if (result.pluginEnabled === false) {
off.classList.toggle("is-outlined"); off.classList.toggle("is-outlined");
} else if (result.pluginEnabled === false) {
on.classList.toggle("is-outlined");
} }
}); });
on.addEventListener("click", function() { on.addEventListener("click", function() {

20
rpc.js
View file

@ -1,20 +0,0 @@
const rpc = require('discord-rpc');
const clientId = '1351391108088987748';
rpc.register(clientId);
const client = new rpc.Client({ transport: 'ipc' });
client.on('ready', () => {
console.log('RPC запущен!');
client.setActivity({
details: 'Играю в вашу игру',
state: 'Нахожусь на уровне 1',
startTimestamp: new Date(),
// largeImageKey: 'image_key', // замените на ключ изображения
// largeImageText: 'Дополнительный текст',
instance: false,
});
});
client.login({ clientId }).catch(console.error);

View file

@ -1 +1,33 @@
importScripts("rpc.js") async function send_rpc_request(data) {
try {
const response = await fetch('http://127.0.0.1:2006/set_presence', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
console.log(response.type)
} catch (error) {
console.error('Error:', error);
}
}
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.type === "send_rpc_request") {
send_rpc_request({
episode: request.episode,
season: request.season,
timestamps: request.timestamps,
name: request.name,
image_url: request.image_url
})
console.log(request.episode, request.season, request.timestamps)
}
}
);