149 | );
150 | };
151 |
152 | export default InfoOverview;
153 |
--------------------------------------------------------------------------------
/weather-app-backend/server.js:
--------------------------------------------------------------------------------
1 | //server.js
2 | const express = require("express");
3 | require("dotenv").config();
4 | const locationRoutes = require("./routes/locationRoutes");
5 | const weatherRoutes = require("./routes/weatherRoutes");
6 | const { getLocationCoordinates } = require("./controllers/locationController");
7 | const dramaticWeatherRoutes = require("./routes/dramaticWeatherRoutes");
8 | const path = require("path");
9 | const cors = require("cors");
10 | const bodyParser = require('body-parser');
11 |
12 |
13 | const app = express();
14 | app.use(bodyParser.json({limit: '50mb'})); // Increase JSON limit
15 | app.use(bodyParser.urlencoded({limit: '50mb', extended: true})); // Increase URL-encoded limit
16 |
17 | app.set("trust proxy", true); // This line should be after const app = express();
18 |
19 | const fs = require("fs");
20 |
21 | // Serve ads.txt file
22 | app.get("/ads.txt", (req, res) => {
23 | res.sendFile(path.join(__dirname, "ads.txt"));
24 | });
25 |
26 | app.use(cors("*"));
27 | app.use(express.json());
28 | app.use(express.urlencoded({ extended: true }));
29 | app.use(express.static("public"));
30 | app.set("view engine", "ejs");
31 |
32 | // Define a route for the root URL
33 | // In your server.js, when rendering the index.ejs
34 | app.use("/generateSummary", async (req, res, next) => {
35 | let visitorKey = `Location:${new Date()}`;
36 | let ip = getRealIP(req);
37 | // await db.set(visitorKey, {
38 | // location: req.body.address ?? "",
39 | // ipAddress: ip,
40 | // timestamp: new Date(),
41 | // });
42 | next();
43 | });
44 | app.get("/", (req, res) => {
45 | res.render("index", { latitude: null, longitude: null, audioBase64: null }); // Initially, audioBase64 is null
46 | });
47 |
48 | // Use routes from the routes files
49 | app.use(locationRoutes);
50 | app.use(weatherRoutes);
51 | app.use(dramaticWeatherRoutes);
52 |
53 | const Database = require("@replit/database");
54 | const db = new Database();
55 |
56 | // Function to get the real IP address from the request
57 | const getRealIP = (req) => {
58 | return req.headers["x-forwarded-for"] || req.connection.remoteAddress;
59 | };
60 |
61 | //subscriber code to save to database for retrevial later
62 | app.post("/subscribe", async (req, res) => {
63 | const { name, email } = req.body;
64 | const ipAddress =
65 | req.headers["x-forwarded-for"] || req.connection.remoteAddress;
66 | const timestamp = new Date(); // Capture the timestamp
67 |
68 | try {
69 | // Generate a unique key for the new subscriber
70 | const subscriberKey = `subscriber:${Date.now()}`;
71 |
72 | // Save the subscriber's data along with IP address and timestamp
73 | await db.set(subscriberKey, { name, email, ipAddress, timestamp });
74 |
75 | res.status(200).json({ message: "Subscription successful" });
76 | } catch (error) {
77 | console.error("Error subscribing", error);
78 | res.status(500).json({ message: "Error subscribing" });
79 | }
80 | });
81 |
82 | // Define the /getLocationCoordinates route
83 | app.post("/getLocationCoordinates", async (req, res) => {
84 | try {
85 | const locationInput = req.body.location;
86 |
87 | // Ensure that you have a valid locationInput before proceeding
88 | if (typeof locationInput !== "string" || locationInput.trim() === "") {
89 | console.error("Invalid location input.");
90 | return res.status(400).json({ error: "Invalid location input." });
91 | }
92 |
93 | // Fetch coordinates from your locationController
94 | const coordinates = await getLocationCoordinates(locationInput);
95 | console.log("Coordinates retrieved:", coordinates);
96 |
97 | if (coordinates.latitude && coordinates.longitude) {
98 | // Log success in retrieving coordinates
99 | console.log("Successfully retrieved coordinates:", coordinates);
100 |
101 | // Send the coordinates as a response
102 | res.json(coordinates);
103 | } else {
104 | console.error("Could not retrieve coordinates.");
105 | res.status(500).json({ error: "Could not retrieve coordinates." });
106 | }
107 | } catch (error) {
108 | console.error("Server error:", error.message);
109 | res.status(500).json({ error: "An error occurred" });
110 | }
111 | });
112 |
113 | // Define a route to handle location submissions
114 | app.post("/submitLocation", async (req, res) => {
115 | try {
116 | const locationInput = req.body.location;
117 | const ipAddress = getRealIP(req); // Get the real IP address from the request
118 | const timestamp = new Date(); // Capture the current timestamp
119 |
120 | // Ensure that you have a valid locationInput before proceeding
121 | if (typeof locationInput !== "string" || locationInput.trim() === "") {
122 | console.error("Invalid location input.");
123 | return res.status(400).json({ error: "Invalid location input." });
124 | }
125 |
126 | // Generate a unique key for the location submission
127 | // We'll use a ternary operator to determine the prefix based on your criteria
128 | const locationSubmissionPrefix = locationInput.startsWith("Location:")
129 | ? "Location:"
130 | : "location:";
131 | const locationSubmissionKey = `${locationSubmissionPrefix}${Date.now()}`;
132 |
133 | console.log(`Storing location data under key: ${locationSubmissionKey}`);
134 |
135 | // Save the location submission's data
136 | await db.set(locationSubmissionKey, {
137 | location: locationInput,
138 | ipAddress: ipAddress,
139 | timestamp: timestamp.toISOString(),
140 | });
141 |
142 | console.log(`Data stored under key: ${locationSubmissionKey}`);
143 |
144 | // Immediately try to retrieve the stored data to verify
145 | const storedData = await db.get(locationSubmissionKey);
146 | console.log(`Retrieved data for verification:`, storedData);
147 |
148 | // Send a success response
149 | res.status(200).json({ message: "Location submission successful" });
150 | } catch (error) {
151 | console.error("Server error:", error.message);
152 | res.status(500).json({ error: "An error occurred" });
153 | }
154 | });
155 |
156 | // Start the server
157 | const PORT = process.env.PORT || 8000;
158 | app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
159 |
--------------------------------------------------------------------------------
/weather-app-backend/controllers/dramaticWeatherController.js:
--------------------------------------------------------------------------------
1 | //dramaticWeatherController.js
2 | const axios = require("axios");
3 | const OpenAI = require("openai");
4 | const { geocode } = require("./geocodingController");
5 | const openai = new OpenAI(process.env.OPENAI_API_KEY);
6 | const WALKSCORE_API_KEY = process.env.WALKSCORE_API_KEY; // Ensure you have this in your .env file
7 |
8 | async function textToSpeech(text) {
9 | try {
10 | const response = await openai.audio.speech.create({
11 | model: "tts-1",
12 | voice: "onyx",
13 | input: text,
14 | });
15 | const buffer = Buffer.from(await response.arrayBuffer());
16 | const audioBase64 = buffer.toString("base64");
17 | return { audioBase64 };
18 | } catch (error) {
19 | console.error("Error in text-to-speech conversion:", error);
20 | throw error;
21 | }
22 | }
23 |
24 | async function generateImage(prompt) {
25 | try {
26 | const response = await axios.post(
27 | "https://api.openai.com/v1/images/generations",
28 | { model: "dall-e-3", prompt: prompt, n: 1, size: "1024x1024" },
29 | {
30 | headers: {
31 | Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
32 | "Content-Type": "application/json",
33 | },
34 | }
35 | );
36 | return response.data.data[0].url;
37 | } catch (error) {
38 | console.error("Error in image generation:", error);
39 | throw error;
40 | }
41 | }
42 |
43 | // async function getWalkScore(lat, lon, address, WALKSCORE_API_KEY) {
44 | // try {
45 | // address = await geocode(lat, lon);
46 | // Construct the API URL with query parameters
47 | // const url = `https://api.walkscore.com/score?format=json&address=${encodeURIComponent(
48 | // address
49 | // )}&lat=${lat}&lon=${lon}&wsapikey=${WALKSCORE_API_KEY}`;
50 |
51 | // Make an HTTP GET request to the Walk Score API
52 | // const response = await axios.get(url);
53 |
54 | // Return the API response data
55 | // return response.data;
56 | // } catch (error) {
57 | // console.error("Error fetching Walk Score:", error);
58 | // return null; // Return null or handle the error as appropriate
59 | // }
60 | //}
61 |
62 | const generateDramaticSummary = async (req, res) => {
63 | if (!Array.isArray(req.body.dailyForecasts)) {
64 | return res
65 | .status(400)
66 | .json({ error: "Invalid weather data format: Expected an array." });
67 | }
68 |
69 | // let walkScore;
70 | // try {
71 | // if (req.body.summaryType && req.body.summaryType === "creative")
72 | // walkScore = await getWalkScore(req.body.lat, req.body.lon, req.body.address, WALKSCORE_API_KEY)
73 | // }
74 | //catch (err) {
75 | // console.error("Failed to get walkscore")
76 | //}
77 |
78 | const prompt = createPrompt(
79 | req.body.dailyForecasts,
80 | req.body.summaryType ?? "creative"
81 | );
82 |
83 | const createImagePrompt = (summary) => {
84 | // Append the instruction to the summary to form the image prompt
85 | return `${summary} Please don't produce any text or words in your final image, only the image without text in the image. I don't want any numbers or character letters. Just a creative image only. Please don't use any characters or text in the image - it's important to me and my livelyhood!`;
86 | };
87 |
88 | try {
89 | const context = createContext(req.body.summaryType ?? "creative");
90 | const chatResponse = await axios.post(
91 | "https://api.openai.com/v1/chat/completions",
92 | {
93 | model: "gpt-4-1106-preview",
94 | messages: [
95 | {
96 | role: "system",
97 | content: context,
98 | },
99 | { role: "user", content: prompt },
100 | ],
101 | },
102 | {
103 | headers: {
104 | Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
105 | "Content-Type": "application/json",
106 | },
107 | }
108 | );
109 |
110 | const summary = chatResponse.data.choices[0].message.content;
111 | let audioData;
112 | if (req.body.audio) {
113 | audioData = await textToSpeech(summary);
114 | }
115 | // Create a prompt for the image that requests no text
116 | const imagePrompt = createImagePrompt(summary);
117 |
118 | // Now call the function to generate the image with the image-specific prompt
119 | let imageUrl;
120 | if (req.body.picture) imageUrl = await generateImage(imagePrompt);
121 | console.log({ reqbody: req.body });
122 |
123 | res.json({ summary, audioBase64: audioData?.audioBase64, imageUrl });
124 | } catch (error) {
125 | console.error("Error while generating dramatic summary:", error);
126 | res.status(500).json({
127 | error: "Error while generating dramatic summary.",
128 | details: error.message,
129 | });
130 | }
131 | };
132 |
133 | const createContext = (promptType) => {
134 | if (promptType === "pro") {
135 | return `You are a professional weather reporter, giving clear explanations of the weather data.`;
136 | } else
137 | return `You are a witty and funny AI who creates dramatic summaries of weather data.`;
138 | };
139 |
140 | const createPrompt = (
141 | weatherData,
142 | promptType = "creative",
143 | walkScore = null
144 | ) => {
145 | let prompt =
146 | "Here's the weather data for the upcoming week, you will tell me a dramatic narative about it:";
147 |
148 | weatherData.forEach((day, index) => {
149 | prompt += `On ${new Date(day.date).toDateString()}, a high of ${day.highTemp
150 | }°F and a low of ${day.lowTemp}°F. `;
151 | prompt += `The day will be marked by ${day.weatherDescription
152 | }, with a ${Math.round(
153 | day.precipitationProbability * 100
154 | )}% chance of precipitation. `;
155 | prompt += `Winds will travel at ${day.windSpeed
156 | } mph, coming from the ${degreesToCardinal(day.windDirection)}. `;
157 | if (index !== weatherData.length - 1) prompt += "And then, ";
158 | });
159 |
160 | if (promptType === "pro") {
161 | prompt += "Now give me 5 short sentences about this weather data";
162 | } else {
163 | prompt += `Now give me about 5 sentences about this weather data and make it funny and or dramatic! Only choose really creative sentences and don't make it more than 5 sentences.`;
164 | }
165 | return prompt;
166 | };
167 |
168 | // Helper function to convert degrees to cardinal directions
169 | const degreesToCardinal = (deg) => {
170 | const val = Math.floor(deg / 22.5 + 0.5);
171 | const cardinalDirections = [
172 | "North",
173 | "North-North-East",
174 | "North-East",
175 | "East_North_East",
176 | "East",
177 | "East-South-East",
178 | "South-East",
179 | "South-South-East",
180 | "South",
181 | "South-South-West",
182 | "Wouth-West",
183 | "West-South-West",
184 | "West",
185 | "West-North-West",
186 | "North-West",
187 | "North-North-West",
188 | ];
189 | return cardinalDirections[val % 16];
190 | };
191 |
192 | module.exports = { generateDramaticSummary };
193 |
--------------------------------------------------------------------------------
/weather-app-backend/public/styles.css:
--------------------------------------------------------------------------------
1 | /* Base styles */
2 | body {
3 | background: linear-gradient(to right, #f7f7f7, #ffffff);
4 | font-family: "Roboto", sans-serif;
5 | padding-top: 20px;
6 | padding-bottom: 20px;
7 | }
8 |
9 | .container {
10 | max-width: 800px;
11 | margin: 0 auto;
12 | padding: 0 20px;
13 | }
14 |
15 | .play-button {
16 | margin-top: 10px;
17 | display: block;
18 | }
19 |
20 | input[type="text"] {
21 | border: 1px solid #ccc;
22 | border-radius: 4px;
23 | padding: 10px;
24 | width: 100%; /* Change to 100% for full width on mobile */
25 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
26 | transition: border-color 0.3s;
27 | }
28 |
29 | button {
30 | background-color: #4caf50;
31 | color: white;
32 | padding: 10px 20px;
33 | border: none;
34 | border-radius: 4px;
35 | cursor: pointer;
36 | transition: background-color 0.3s, box-shadow 0.3s;
37 | width: 100%; /* Full width on mobile */
38 | }
39 |
40 | button:hover {
41 | background-color: #45a049;
42 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
43 | }
44 |
45 | h1 {
46 | color: #333;
47 | text-align: center;
48 | }
49 |
50 | h3 {
51 | color: #333;
52 | text-align: center;
53 | font-size: medium; /* Reduce the font size */
54 | font-style: italic; /* Italicize the text */
55 | }
56 |
57 | h2 {
58 | color: #45a049;
59 | text-align: center;
60 | }
61 |
62 | footer {
63 | text-align: center;
64 | padding: 20px;
65 | font-size: 0.8em;
66 | }
67 |
68 | audio {
69 | display: block;
70 | max-width: 100%;
71 | margin: 20px auto; /* Center the audio player */
72 | }
73 |
74 | /* Responsive styles */
75 | @media (max-width: 768px) {
76 | .container {
77 | padding: 0 10px; /* Less padding on smaller screens */
78 | }
79 |
80 | input[type="text"],
81 | button {
82 | width: 100%; /* Full width on mobile */
83 | }
84 | }
85 |
86 | /* Style the form elements */
87 | #locationForm {
88 | text-align: center; /* Center the form contents */
89 | }
90 |
91 | /* Style the input field */
92 | #locationInput {
93 | width: 50%; /* Smaller width */
94 | max-width: 300px; /* Maximum width it can grow to */
95 | margin: 0 auto; /* Center the input field */
96 | }
97 |
98 | /* Style the button */
99 | .submit-button {
100 | width: 50%; /* Smaller width */
101 | max-width: 150px; /* Maximum width it can grow to */
102 | margin: 10px auto; /* Space above the button and center it */
103 | display: block; /* Block level to fill width of container */
104 | }
105 |
106 | /* You might also want to style the container of the form to ensure it's centered */
107 | .container {
108 | width: 100%; /* Use the full width */
109 | max-width: 600px; /* Maximum width of the container */
110 | margin: 0 auto; /* Center the container */
111 | padding: 0 20px; /* Padding on the sides */
112 | }
113 |
114 | /* Center and size the loading GIF */
115 | #loadingGif {
116 | display: block; /* Use block to allow margin auto to work */
117 | margin: 10px auto; /* Center the image with auto margin */
118 | max-width: 200px; /* Maximum width of the GIF */
119 | height: auto; /* Maintain aspect ratio */
120 | width: 30%; /* Reduces the width to 50% of its original size */
121 | }
122 | /* General styles, apply on all devices */
123 | #generatedImage {
124 | width: 100%; /* Full width on mobile devices */
125 | height: auto; /* Maintain aspect ratio */
126 | max-width: 1024px; /* Limit the size of the image to not exceed 1024px */
127 | display: block; /* To prevent inline behavior */
128 | margin: 20px auto; /* Center the image with margin */
129 | }
130 |
131 | /* Desktop specific styles */
132 | @media (min-width: 768px) {
133 | /* Adjust the min-width as needed for desktop breakpoint */
134 | #generatedImage {
135 | width: 40%; /* Half width on desktop */
136 | }
137 | }
138 |
139 | .small-link {
140 | font-size: 0.8em;
141 | color: #000; /* Or any color you prefer */
142 | }
143 |
144 | /* Apply base styles to all input fields for consistency */
145 | input[type="text"],
146 | input[type="email"],
147 | button {
148 | border: 1px solid #ccc;
149 | border-radius: 4px;
150 | padding: 10px;
151 | width: 100%; /* Change to 100% for full width on mobile */
152 | box-sizing: border-box; /* Include padding and border in the width */
153 | margin-bottom: 10px; /* Add some space below each input field */
154 | }
155 |
156 | /* Style the subscribe button similarly to the submit button */
157 | #subscribeForm button {
158 | background-color: #4caf50; /* Same green color */
159 | color: white;
160 | padding: 10px 20px;
161 | border: none;
162 | border-radius: 4px;
163 | cursor: pointer;
164 | transition: background-color 0.3s, box-shadow 0.3s;
165 | }
166 |
167 | /* Ensure the form items are centered and responsive */
168 | #subscribeForm input,
169 | #subscribeForm button {
170 | max-width: 300px; /* Control max width */
171 | margin: 10px auto; /* Centering */
172 | display: block; /* Stack elements */
173 | }
174 |
175 | /* Add some spacing and styling to the footer for better appeal */
176 | footer {
177 | text-align: center;
178 | padding: 40px 20px; /* More padding */
179 | background-color: #f9f9f9; /* Light grey background */
180 | border-top: 1px solid #eaeaea; /* Add a border to the top */
181 | font-size: 0.9em; /* Slightly larger font size */
182 | }
183 |
184 | .hidden {
185 | display: none;
186 | }
187 |
188 | /* Set the progress bar container to use flexbox and center its children */
189 | #progressBarContainer {
190 | width: 35%; /* Set the desired width of the progress bar container */
191 | margin: 0 auto; /* Center the container within its parent */
192 | background-color: #ddd;
193 | display: none; /* Start with the progress bar hidden */
194 | }
195 |
196 | /* The progress bar itself */
197 | #progressBar {
198 | width: 0%; /* Start the progress bar as empty */
199 | max-width: 100%; /* Allow the progress bar to fill the container */
200 | height: 22px;
201 | background-color: #4caf50;
202 | transition: width 17s linear;
203 | }
204 |
205 | /* Media query for mobile devices */
206 | @media (max-width: 768px) {
207 | #progressBarContainer {
208 | width: calc(100% - 40px); /* Adjusted container width to provide padding */
209 | margin: 10px auto; /* Centered the container with automatic margins */
210 | }
211 | }
212 |
213 | .centered-link {
214 | text-align: center;
215 | margin-top: 20px; /* Adjusted margin-top for spacing */
216 | }
217 |
218 | .centered-link a {
219 | text-decoration: none;
220 | color: #1766b5; /* Custom link color */
221 | font-weight: bold; /* Bold text */
222 | font-size: 14px; /* Adjusted font size */
223 | }
224 |
225 | .line-space {
226 | height: 20px; /* Line spacing */
227 | }
228 |
229 | /* Additional CSS */
230 | .walk-score-container {
231 | text-align: center; /* Center alignment for the text */
232 | margin-top: 20px; /* Space above the container */
233 | }
234 |
235 | .walk-score-container img {
236 | max-width: 100%; /* Image is responsive within the container */
237 | height: auto; /* Maintain aspect ratio */
238 | }
239 |
240 | /* Style improvements for links */
241 | .walk-score-container a {
242 | display: inline-block;
243 | margin: 5px;
244 | text-decoration: none;
245 | color: #007bff; /* Link color */
246 | }
247 |
248 | /* Styles for the header banner */
249 | .header-banner {
250 | width: 100%; /* Full width on smaller screens */
251 | max-width: 800px; /* Maximum width set to 1000px */
252 | height: auto; /* Maintain aspect ratio */
253 | display: block; /* Block display */
254 | margin: 0 auto; /* Center the banner */
255 | }
256 |
257 | /* Adjust header image size for smaller screens */
258 | @media (max-width: 768px) {
259 | .header-banner {
260 | width: 100%; /* Full width on smaller screens */
261 | height: auto; /* Maintain aspect ratio */
262 | }
263 | }
264 |
265 | .ad-container {
266 | text-align: center;
267 | margin-top: 20px;
268 | margin-bottom: 20px;
269 | min-height: 100px; /* Minimum height to ensure space for the ad */
270 | width: 100%; /* Ensure the container spans the width of its parent */
271 | display: block; /* This can be adjusted based on your layout needs */
272 | }
273 |
--------------------------------------------------------------------------------
/weather-app-backend/public/privacy-policy.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Privacy Policy - Google Drive File Visualizer
7 |
8 |
9 |
10 |
15 |
16 |
17 |
21 |
30 |
31 |
Privacy Policy for Weather Talk LLC
32 |
33 |
34 | At Weather Talk, accessible from weathertalk.ai, one of our main
35 | priorities is the privacy of our visitors. This Privacy Policy document
36 | contains types of information that is collected and recorded by Weather
37 | Talk and how we use it.
38 |
39 |
40 |
41 |
42 | Now here's the thing: we literally have no idea who you are and where
43 | you live or which location you searched. Nothin is stored on our
44 | servers; we have no database and don't collect or retain any
45 | information. When you search a location, it doesn't store that; again,
46 | we have no idea. We have no idea who you are. There is no personal
47 | infomation we collect. Have fun and don't worry about any location you
48 | search for: it's just for fun!
49 |
50 |
51 |
52 |
53 | If you have additional questions or require more information about our
54 | Privacy Policy, do not hesitate to contact us.
55 |
56 |
57 |
58 | This Privacy Policy applies only to our online activities and is valid for
59 | visitors to our website with regards to the information that they shared
60 | and/or collect in Weather Talk. This policy is not applicable to any
61 | information collected offline or via channels other than this website.
62 |
63 |
64 |
Consent
65 |
66 |
67 | By using our website, you hereby consent to our Privacy Policy and agree
68 | to its terms.
69 |
70 |
71 |
Information we collect
72 |
73 |
74 | We collect no personal information. You can search a location and see fun
75 | things about the weather, but we have no idea who you are!
76 |
77 |
78 |
79 | The personal information that you are asked to provide, and the reasons
80 | why you are asked to provide it, will be made clear to you at the point we
81 | ask you to provide your personal information.
82 |
83 |
84 | If you contact us directly, we may receive additional information about
85 | you such as your name, email address, phone number, the contents of the
86 | message and/or attachments you may send us, and any other information you
87 | may choose to provide.
88 |
89 |
90 |
How we use your information
91 |
92 |
We use the information we collect in various ways, including to:
93 |
94 |
95 | Communicate with you, either directly or through one of our partners,
96 | including for customer service, to provide you with updates and other
97 | information relating to the website, and for marketing and promotional
98 | purposes
99 |
Send you emails
100 |
101 |
102 |
Log Files
103 |
104 |
105 | Weather Talk follows a standard procedure of using log files. These files
106 | log visitors when they visit websites. All hosting companies do this and a
107 | part of hosting services' analytics. The information collected by log
108 | files include internet protocol (IP) addresses, browser type, Internet
109 | Service Provider (ISP), date and time stamp, referring/exit pages, and
110 | possibly the number of clicks. These are not linked to any information
111 | that is personally identifiable. The purpose of the information is for
112 | analyzing trends, administering the site, tracking users' movement on the
113 | website, and gathering demographic information.
114 |
115 |
116 |
Advertising Partners Privacy Policies
117 |
118 |
We do not collect your infomation for advertising purposes.
119 |
120 |
121 | Third-party ad servers or ad networks uses technologies like cookies,
122 | JavaScript, or Web Beacons that are used in their respective
123 | advertisements and links that appear on Weather Talk, which are sent
124 | directly to users' browser. They automatically receive your IP address
125 | when this occurs. These technologies are used to measure the effectiveness
126 | of their advertising campaigns and/or to personalize the advertising
127 | content that you see on websites that you visit.
128 |
129 |
130 |
Third Party Privacy Policies
131 |
132 |
133 | Weather Talk's Privacy Policy does not apply to other advertisers or
134 | websites. Thus, we are advising you to consult the respective Privacy
135 | Policies of these third-party ad servers for more detailed information. It
136 | may include their practices and instructions about how to opt-out of
137 | certain options.
138 |
139 |
140 |
141 | You can choose to disable cookies through your individual browser options.
142 | To know more detailed information about cookie management with specific
143 | web browsers, it can be found at the browsers' respective websites.
144 |
145 |
146 |
CCPA Privacy Rights (Do Not Sell My Personal Information)
147 |
148 |
149 | Under the CCPA, among other rights, California consumers have the right
150 | to:
151 |
152 |
153 | Request that a business that collects a consumer's personal data disclose
154 | the categories and specific pieces of personal data that a business has
155 | collected about consumers.
156 |
157 |
158 | Request that a business delete any personal data about the consumer that a
159 | business has collected.
160 |
161 |
162 | Request that a business that sells a consumer's personal data, not sell
163 | the consumer's personal data.
164 |
165 |
166 | If you make a request, we have one month to respond to you. If you would
167 | like to exercise any of these rights, please contact us.
168 |
169 |
170 |
GDPR Data Protection Rights
171 |
172 |
173 | We would like to make sure you are fully aware of all of your data
174 | protection rights. Every user is entitled to the following:
175 |
176 |
177 | The right to access – You have the right to request copies of your
178 | personal data. We may charge you a small fee for this service.
179 |
180 |
181 | We will then tell you.... we have zero personal information on you unless
182 | you subscribe to us - then we will have your name and email -- that's it!
183 | We do not store location information and even if we did, we have no idea
184 | who searched for that location.
185 |
186 |
187 | The right to rectification – You have the right to request that we correct
188 | any information you believe is inaccurate. You also have the right to
189 | request that we complete the information you believe is incomplete.
190 |
191 |
192 | The right to erasure – You have the right to request that we erase your
193 | personal data, under certain conditions.
194 |
195 |
196 | The right to restrict processing – You have the right to request that we
197 | restrict the processing of your personal data, under certain conditions.
198 |
199 |
200 | The right to object to processing – You have the right to object to our
201 | processing of your personal data, under certain conditions.
202 |
203 |
204 | The right to data portability – You have the right to request that we
205 | transfer the data that we have collected to another organization, or
206 | directly to you, under certain conditions.
207 |
208 |
209 | If you make a request, we have one month to respond to you. If you would
210 | like to exercise any of these rights, please contact us.
211 |
212 |
213 |
Children's Information
214 |
215 |
216 | Another part of our priority is adding protection for children while using
217 | the internet. We encourage parents and guardians to observe, participate
218 | in, and/or monitor and guide their online activity.
219 |
220 |
221 |
222 | Weather Talk does not knowingly collect any Personal Identifiable
223 | Information from children under the age of 13. If you think that your
224 | child provided this kind of information on our website, we strongly
225 | encourage you to contact us immediately and we will do our best efforts to
226 | promptly remove such information from our records.
227 |
228 |
229 |
Changes to This Privacy Policy
230 |
231 |
232 | We may update our Privacy Policy from time to time. Thus, we advise you to
233 | review this page periodically for any changes. We will notify you of any
234 | changes by posting the new Privacy Policy on this page. These changes are
235 | effective immediately, after they are posted on this page.
236 |
237 |
238 |
Contact Us
239 |
240 |
241 | If you have any questions or suggestions about our Privacy Policy, do not
242 | hesitate to contact us.
243 |
97 | When entering a location, we will only fetch information on that location. We know nothing about you -- not
98 | even your location. When you click "submit" it will load the weather data in a chosen location and provide a
99 | response.
100 |
163 | I'd love to share my code and how I did this, which I will put soon on my YouTube page I just
165 | created. If you want to know how this was made, please email me (contact info below) and I can share
166 | code or walk through it with you!
167 |