RSS订阅

https://files.wowood.cn/feeds/MP_WXS_3945205084.atom.rss?limit=50

https://files.wowood.cn/feeds/MP_WXS_3922264679.atom.rss?limit=50

https://files.wowood.cn/feeds/MP_WXS_3888889046.atom.rss?limit=50

https://files.wowood.cn/feeds/MP_WXS_3898080835.atom.rss?limit=50

 

获取前50

https://github.com/cooderl/wewe-rss/issues/213

 

Docker部署

环境要求:Git、Docker、Docker-Compose

克隆项目

git clone https://github.com/srcrs/rss-reader

进入rss-reader文件夹,运行项目

docker-compose up -d

国内服务器将Dockerfile中取消下面注释使用 go mod 镜像

#RUN go env -w GO111MODULE=on && \
#    go env -w GOPROXY=https://goproxy.cn,direct
部署成功后,通过ip+端口号访问

 

如果要修改主题

1.拷贝项目后,直接修改/globals/static/index.html 文件
2.修改完成后,重新打包镜像

docker build -t srcrs/rss-reader:latest .

如果连接不上docker服务,则使用https://docker.1ms.run

想起之前的问题所在了,小猫咪开全局的话宝塔面板会直接崩溃,只能在进入终端之后再打开全局进行拉取,要不断换节点试试,有时候直接规则就能拉到了

curl -s https://static.1ms.run/1ms-helper/scripts/install.sh | sudo bash /dev/stdin config

3.重新启动项目即可

docker compose down
docker compose up -d

 

代码备份

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>RSS Reader</title>
  <meta name="description" content='RSS Reader,一个将订阅信息聚合展示的开源web工具,便于了解近期关注的信息,同时页面数据数据也实现了自动刷新。'>
  <meta name="keywords" content="RSS, news, feed, reader, open-source">
  <meta name="anthor" content="srcrs">
  <style>
    /* * Element Plus V2.x CSS - Condensed for inline use 
     * Note: This is a very simplified version of Element Plus CSS. 
     * For a complete and accurate inline CSS, you'd typically need to extract 
     * all relevant styles from 'index.min.css' and 'dark-mode.css' 
     * and potentially their dependencies, which is a massive undertaking.
     * For demonstration purposes, only essential layout styles are included.
    */
    html {
      line-height: 1.15;
      -webkit-text-size-adjust: 100%;
    }

    body {
      margin: 0;
      font-family: var(--el-font-family);
      font-size: var(--el-font-size-base);
      line-height: var(--el-line-height-base);
      color: var(--el-text-color-primary);
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
    }

    :root {
      --el-color-white: #ffffff;
      --el-color-black: #000000;
      --el-color-primary: #409eff;
      --el-color-primary-light-3: #79bbff;
      --el-color-primary-light-5: #a0cfff;
      --el-color-primary-light-7: #c6e2ff;
      --el-color-primary-light-8: #d9ecff;
      --el-color-primary-light-9: #ecf5ff;
      --el-color-primary-dark-2: #337ecc;
      --el-color-success: #67c23a;
      --el-color-warning: #e6a23c;
      --el-color-danger: #f56c6c;
      --el-color-info: #909399;
      --el-bg-color: #ffffff;
      --el-bg-color-overlay: #ffffff;
      --el-text-color-primary: #303133;
      --el-text-color-regular: #606266;
      --el-text-color-secondary: #909399;
      --el-text-color-placeholder: #a8abb2;
      --el-border-color: #dcdfe6;
      --el-border-color-light: #e4e7ed;
      --el-border-color-lighter: #ebeef5;
      --el-border-color-extra-light: #f2f6fc;
      --el-border-color-dark: #d4d7de;
      --el-border-color-darker: #cdd0d6;
      --el-fill-color: #f0f2f5;
      --el-fill-color-light: #f5f7fa;
      --el-fill-color-lighter: #fafafa;
      --el-fill-color-extra-light: #edf2f6;
      --el-fill-color-dark: #ebedef;
      --el-fill-color-darker: #e6e8eb;
      --el-box-shadow: 0px 12px 32px 4px rgba(0, 0, 0, 0.04), 0px 8px 20px rgba(0, 0, 0, 0.08);
      --el-box-shadow-light: 0px 0px 12px rgba(0, 0, 0, 0.12);
      --el-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, 0.12);
      --el-box-shadow-dark: 0px 16px 48px 16px rgba(0, 0, 0, 0.08), 0px 12px 32px rgba(0, 0, 0, 0.12), 0px 8px 16px -8px rgba(0, 0, 0, 0.16);
      --el-font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "\5FAE\8F6F\96C5\9ED1", Arial, sans-serif;
      --el-font-size-extra-large: 20px;
      --el-font-size-large: 18px;
      --el-font-size-medium: 16px;
      --el-font-size-base: 14px;
      --el-font-size-small: 13px;
      --el-font-size-extra-small: 12px;
      --el-font-weight-primary: 500;
      --el-font-line-height-primary: 24px;
      --el-index-normal: 1;
      --el-index-top: 1000;
      --el-index-popper: 2000;
      --el-border-radius-base: 4px;
      --el-border-radius-small: 2px;
      --el-border-radius-round: 20px;
      --el-border-radius-circle: 100%;
      --el-transition-duration: 0.3s;
      --el-transition-duration-fast: 0.2s;
      --el-transition-function-ease-in-out-bezier: cubic-bezier(0.645, 0.045, 0.355, 1);
      --el-transition-function-linear: linear;
      --el-disabled-opacity: 0.5;
      --el-box-shadow-base: var(--el-box-shadow-light);
    }

    .el-container {
      display: flex;
      flex-direction: column;
      height: 100%;
    }

    .el-header {
      padding: var(--el-header-padding);
      box-sizing: border-box;
      flex-shrink: 0;
    }

    .el-main {
      flex-grow: 1;
      padding: var(--el-main-padding);
    }

    .el-row {
      display: flex;
      flex-wrap: wrap;
      position: relative;
      box-sizing: border-box;
    }

    .el-row::before,
    .el-row::after {
      display: table;
      content: "";
    }

    .el-row::after {
      clear: both;
    }

    .el-col {
      float: left;
      box-sizing: border-box;
    }

    .el-card {
      border-radius: var(--el-border-radius-base);
      border: 1px solid var(--el-border-color-light);
      background-color: var(--el-color-white);
      overflow: hidden;
      color: var(--el-text-color-primary);
      transition: 0.3s;
    }

    .el-card__header {
      padding: 18px 20px;
      border-bottom: 1px solid var(--el-border-color-light);
      box-sizing: border-box;
    }

    .el-card__body {
      padding: 20px; /* Adjusted padding for 432px height */
    }

    .el-card__footer {
      padding: 18px 20px;
      border-top: 1px solid var(--el-border-color-light);
      box-sizing: border-box;
    }

    .el-link {
      display: inline-flex;
      flex-direction: row;
      align-items: center;
      vertical-align: middle;
      position: relative;
      text-decoration: none;
      outline: none;
      cursor: pointer;
      padding: 0;
      font-size: inherit;
      font-weight: inherit;
    }

    /* Override Element Plus's custom underline behavior for el-link to use standard text-decoration */
    .el-link.is-underline:hover:after {
      content: none; /* Remove the custom pseudo-element underline */
    }

    .el-scrollbar {
      overflow: hidden;
      position: relative;
    }

    .el-scrollbar__wrap {
      overflow: auto;
      height: 100%;
    }

    .el-scrollbar__view {
      box-sizing: content-box;
    }

    .el-loading-mask {
      position: absolute;
      z-index: 2000;
      background-color: var(--el-overlay-color-light);
      margin: 0;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      transition: opacity 0.3s;
    }

    .el-loading-spinner {
      top: 50%;
      margin-top: -21px;
      width: 100%;
      text-align: center;
      position: absolute;
    }

    .el-loading-text {
      color: var(--el-color-primary);
      margin: 3px 0;
      font-size: 14px;
    }

    /* Custom Styles */
    body {
      font-family: "Avenir", Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 0; /* Remove body top margin, controlled by el-main padding */
      background-color: #f2f2f2; /* Light gray background */
    }

    #app {
      max-width: 1800px; /* Increased max-width to allow more content horizontally */
      margin: 0 auto; /* Center the app */
    }

    .card-header {
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 18px;
      font-weight: bold;
      color: #333; /* Darker header text */
      background-color: #e6e6e6; /* Light gray background for header */
      padding: 10px 0;
      border-bottom: 1px solid #ddd;
    }

    .el-card {
      height: 100%; /* Make cards fill the column height */
      display: flex;
      flex-direction: column;
      margin-bottom: 0px; /* Add margin between cards */
      border: none; /* Remove default border */
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* Subtle shadow */
    }

    .el-card__header {
      padding: 0; /* Remove default padding as card-header handles it */
      border-bottom: none;
    }

    /* el-card__body padding is already adjusted in Element Plus styles */

    .list-item {
      display: flex;
      align-items: flex-start; /* Align items to the start */
      text-align: left; /* Ensure text alignment is left */
      width: 100%;
      margin-bottom: 10px; /* Space between list items */
    }

    .list-item:last-child {
      margin-bottom: 0; /* No margin for last item */
    }

    .list-item-title {
      display: flex;
      flex-grow: 1;
      text-align: left; /* Ensure text alignment is left */
      width: 100%;
      line-height: 1.4; /* Improve readability */
    }

    .list-item-title span {
      margin-right: 0; /* Removed space after number */
      color: #666; /* Gray out numbers */
      font-size: 14px;
      /* Prevent number from wrapping if link is very short */
      flex-shrink: 0;
    }

    .el-link {
      color: #333; /* Darker link color */
      text-decoration: none;
      font-size: 14px;
      flex-grow: 1; /* Allow link to take full width */
      overflow: hidden; /* Hide overflow text */
      text-overflow: ellipsis; /* Add ellipsis */
      white-space: nowrap; /* Prevent text wrapping */
    }

    .el-link:hover {
      color: #409eff !important; /* Element Plus primary color on hover, !important to ensure override */
      text-decoration: underline !important; /* Add underline on hover, !important to ensure override */
    }

    .feed-col {
      margin-bottom: 20px;
    }

    .time {
      font-size: 12px;
      color: #999;
      display: block; /* Ensure it takes full width */
    }

    .el-card__footer {
      height: auto; /* Allow footer to adjust height */
      padding: 10px 20px; /* Adjust footer padding */
      border-top: 1px solid #eee; /* Light border on footer */
      background-color: #f9f9f9; /* Slightly different background for footer */
      display: flex; /* Added for centering */
      justify-content: center; /* Added for horizontal centering */
      align-items: center; /* Added for vertical centering */
    }

    /* Scrollbar styles for better appearance */
    .el-scrollbar__wrap {
      margin-right: -17px; /* Hide default scrollbar space */
    }

    .el-scrollbar__bar.is-vertical>div {
      background-color: rgba(0, 0, 0, 0.2); /* Darker scrollbar thumb */
      border-radius: 4px;
    }

    /* Responsive adjustments */
    /* Mobile (xs): 1 column */
    @media (max-width: 767px) {
      .card-header {
        font-size: 16px;
      }

      .list-item-title .el-link {
        font-size: 13px;
      }

      .time {
        font-size: 11px;
      }

      .el-col-xs-24 {
        width: 100%;
      }

      .el-main {
        padding: 20px 10px; /* Adjust padding for small screens */
      }
    }

    /* Small screens (sm): 2 columns */
    @media (min-width: 768px) and (max-width: 991px) {
      .el-col-sm-12 {
        width: 50%;
      }

      .el-main {
        padding: 20px;
      }
    }

    /* Medium screens (md): 3 columns */
    @media (min-width: 992px) and (max-width: 1199px) {
      .el-col-md-8 {
        width: 33.33333%;
      }

      .el-main {
        padding: 20px;
      }
    }

    /* Large screens (lg) and up: 4 columns */
    @media (min-width: 1200px) {
      .el-col-lg-6 {
        width: 25%;
      }

      .el-main {
        padding: 20px;
      }
    }
  </style>
</head>

<body>
  <div id="app">
    <el-container>
      <el-main v-loading.fullscreen.lock="fullscreenLoading" element-loading-text="拼命加载中" style="padding-top: 30px;">
        <el-row :gutter="20">
          <el-col v-if="showSEOFlag" :xs="24" :sm="12" :md="8" :lg="6" :key="0" class="feed-col">
            <el-card class="box-card">
              <template #header>
                <div class="card-header">
                  <span>Example Feed 1</span>
                </div>
              </template>
              <el-scrollbar style="height: 392px;">
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>1.</span>
                      <el-link href="#" target="_blank" title="Example Item 1 Title longer text that should now be cut off if too long">
                        Example Item 1 Title longer text that should now be cut off if too long
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>2.</span>
                      <el-link href="#" target="_blank" title="Example Item 2 Title">
                        Example Item 2 Title
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>3.</span>
                      <el-link href="#" target="_blank" title="Another Example Item Title">
                        Another Example Item Title that is very long and should definitely be truncated now instead of wrapping.
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>4.</span>
                      <el-link href="#" target="_blank" title="Short Title">
                        Short Title
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>5.</span>
                      <el-link href="#" target="_blank" title="Another one with some description">
                        Another one with some description to make it longer.
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>6.</span>
                      <el-link href="#" target="_blank" title="More Content Here">
                        More Content Here, allowing for more lines to be visible in the taller card.
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>7.</span>
                      <el-link href="#" target="_blank" title="Seventh Item Example">
                        Seventh Item Example for demonstration.
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>8.</span>
                      <el-link href="#" target="_blank" title="Eighth Item, still more">
                        Eighth Item, still more to fill the space.
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>9.</span>
                      <el-link href="#" target="_blank" title="Ninth Article of Interest">
                        Ninth Article of Interest.
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>10.</span>
                      <el-link href="#" target="_blank" title="Tenth Item in the List">
                        Tenth Item in the List. This should fill about half of the scrollable area now.
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>11.</span>
                      <el-link href="#" target="_blank" title="Eleventh Entry">
                        Eleventh Entry to fill the view.
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>12.</span>
                      <el-link href="#" target="_blank" title="Another long entry that tests the two-line clamp and overall card height to make sure it doesn't overflow unnaturally.">
                        Another long entry that tests the two-line clamp and overall card height to make sure it doesn't overflow unnaturally.
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>13.</span>
                      <el-link href="#" target="_blank" title="Thirteenth item, almost there.">
                        Thirteenth item, almost there.
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>14.</span>
                      <el-link href="#" target="_blank" title="Fourteenth content piece.">
                        Fourteenth content piece.
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>15.</span>
                      <el-link href="#" target="_blank" title="Fifteenth item, final one visible before scrolling.">
                        Fifteenth item, final one visible before scrolling.
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>16.</span>
                      <el-link href="#" target="_blank" title="Sixteenth item, getting closer to 20.">
                        Sixteenth item, getting closer to 20.
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>17.</span>
                      <el-link href="#" target="_blank" title="Seventeenth item.">
                        Seventeenth item.
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>18.</span>
                      <el-link href="#" target="_blank" title="Eighteenth item.">
                        Eighteenth item.
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>19.</span>
                      <el-link href="#" target="_blank" title="Nineteenth item.">
                        Nineteenth item.
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>20.</span>
                      <el-link href="#" target="_blank" title="Twentieth item - this should be near the bottom of the visible area.">
                        Twentieth item - this should be near the bottom of the visible area.
                      </el-link>
                    </div>
                  </div>
                </div>
                <div>
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>21.</span>
                      <el-link href="#" target="_blank" title="Twenty-first item - this should now be scrolled.">
                        Twenty-first item - this should now be scrolled.
                      </el-link>
                    </div>
                  </div>
                </div>
              </el-scrollbar>
              <template #footer>
                <div class="card-footer">
                  <time class="time">
                    2023-10-27 10:00:00
                  </time>
                </div>
              </template>
            </el-card>
          </el-col>

          <el-col :xs="24" :sm="12" :md="8" :lg="6" v-for="(feed, index) in feeds" :key="feed.link || index" class="feed-col">
            <el-card class="box-card">
              <template #header>
                <div class="card-header">
                  <span>{{ feed.title }}</span>
                </div>
              </template>
              <el-scrollbar style="height: 392px;">
                <div v-for="(item, i) in feed.items" :key="item.link || i">
                  <div class="list-item">
                    <div class="list-item-title">
                      <span>{{ i+1 }}.</span>
                      <el-link :href="item.link" target="_blank" :title="item.title">{{ item.title }}</el-link>
                    </div>
                  </div>
                </div>
              </el-scrollbar>
              <template #footer>
                <div class="card-footer">
                  <time class="time">{{ feed.custom ? feed.custom.lastupdate : 'N/A' }}</time>
                </div>
              </template>
            </el-card>
          </el-col>
        </el-row>
      </el-main>
    </el-container>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue@3.4.21/dist/vue.global.prod.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/element-plus@2.7.2/dist/index.full.min.js"></script>
  <script>
    const app = Vue.createApp({
      data() {
        return {
          feeds: [],
          showSEOFlag: true,
          fullscreenLoading: true,
          isPc: true,
          autoUpdatePush: 60,
          heartbeatInterval: null,
        };
      },
      async created() {
        this.fullscreenLoading = false;
        // Determine if on PC based on screen width
        this.isPc = !window.matchMedia('(max-width: 767px)').matches;
      },
      async mounted() {
        const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
        let socket;

        // Function to establish WebSocket connection
        const connectWebSocket = () => {
          socket = new WebSocket(protocol + window.location.host + "/ws");

          socket.onopen = () => {
            console.log("WebSocket connected.");
            // Clear any previous reconnection attempts and intervals
            if (this.heartbeatInterval) {
              clearInterval(this.heartbeatInterval);
            }

            // Start heartbeat only if on PC and auto-update is enabled
            if (this.isPc && this.autoUpdatePush > 0) {
              this.heartbeatInterval = setInterval(sendHeartbeat, 60000); // Heartbeat every 60 seconds
            }
          };

          socket.onmessage = event => {
            const feed = JSON.parse(event.data);
            const existingFeedIndex = this.feeds.findIndex(f => f.link === feed.link);
            if (existingFeedIndex !== -1) {
              // Update existing feed in place to prevent full re-render of the card
              this.feeds[existingFeedIndex] = feed;
            } else {
              // Add new feed
              this.feeds.push(feed);
            }
            // Once data comes from WebSocket, hide the static SEO content
            this.showSEOFlag = false;
          };

          // Function to send heartbeat message
          const sendHeartbeat = () => {
            if (socket.readyState === WebSocket.OPEN) {
              socket.send("heartbeat");
            }
          };

          socket.onclose = event => {
            console.log("WebSocket closed. Attempting to reconnect...");
            // Clear intervals on close
            if (this.heartbeatInterval) {
              clearInterval(this.heartbeatInterval);
            }

            // Attempt to reconnect only if on PC and auto-update is enabled
            if (this.isPc && this.autoUpdatePush > 0) {
              // Only attempt reconnection, no full page reload
              setTimeout(connectWebSocket, 3000); // Reconnect after 3 seconds
            }
          };

          socket.onerror = error => {
            console.error("WebSocket error:", error);
            // Force close to trigger onclose and reconnect logic
            socket.close();
          };
        };

        // Initial WebSocket connection
        connectWebSocket();
      },
      beforeUnmount() {
        // Clean up intervals when component is unmounted
        if (this.heartbeatInterval) {
          clearInterval(this.heartbeatInterval);
        }
      }
    });

    // Register ElementPlus components globally (assuming ElementPlus is properly loaded)
    app.use(ElementPlus);
    app.mount("#app");
  </script>
</body>
</html>